diff --git a/Examples/Nodify.Calculator/CalculatorViewModel.cs b/Examples/Nodify.Calculator/CalculatorViewModel.cs
index 05936ed..63c9a46 100644
--- a/Examples/Nodify.Calculator/CalculatorViewModel.cs
+++ b/Examples/Nodify.Calculator/CalculatorViewModel.cs
@@ -46,6 +46,9 @@ namespace Nodify.Calculator
// Dynamic Knot node: adapt shape/color to match connected wire
if (!IsLoading) HandleKnotNodeConnected(c);
+
+ // Dynamic Parse Json node: adapt input to match source
+ if (!IsLoading) HandleParseJsonNodeConnected(c);
})
.WhenRemoved(c =>
{
@@ -81,6 +84,9 @@ namespace Nodify.Calculator
// Dynamic Knot node: reset on disconnect
HandleKnotNodeDisconnected(c);
+
+ // Dynamic Parse Json node: reset input on disconnect
+ HandleParseJsonNodeDisconnected(c);
});
Operations.WhenAdded(x =>
@@ -854,6 +860,50 @@ namespace Nodify.Calculator
DataType = "object"
});
}
+
+ private void HandleParseJsonNodeConnected(ConnectionViewModel c)
+ {
+ ConnectorViewModel parseInput = null;
+ ConnectorViewModel sourceConnector = null;
+
+ if (c.Input.IsCopyConnector && c.Input.IsInput && c.Input.Operation is ParseJsonOperationViewModel)
+ {
+ parseInput = c.Input;
+ sourceConnector = c.Output;
+ }
+ else if (c.Output.IsCopyConnector && c.Output.IsInput && c.Output.Operation is ParseJsonOperationViewModel)
+ {
+ parseInput = c.Output;
+ sourceConnector = c.Input;
+ }
+
+ if (parseInput == null || sourceConnector == null) return;
+
+ // Adapt input connector to match the source
+ parseInput.Shape = sourceConnector.Shape;
+ parseInput.ConnectorColor = sourceConnector.RawColor;
+ parseInput.DataType = sourceConnector.DataType;
+ }
+
+ private void HandleParseJsonNodeDisconnected(ConnectionViewModel c)
+ {
+ ConnectorViewModel parseInput = null;
+
+ if (c.Input.IsCopyConnector && c.Input.IsInput && c.Input.Operation is ParseJsonOperationViewModel)
+ parseInput = c.Input;
+ else if (c.Output.IsCopyConnector && c.Output.IsInput && c.Output.Operation is ParseJsonOperationViewModel)
+ parseInput = c.Output;
+
+ if (parseInput == null) return;
+
+ var stillConnected = Connections.Any(con => con.Input == parseInput || con.Output == parseInput);
+ if (stillConnected) return;
+
+ // Reset to universal
+ parseInput.Shape = ConnectorShape.Circle;
+ parseInput.ConnectorColor = System.Drawing.Color.White;
+ parseInput.DataType = string.Empty;
+ }
}
}
diff --git a/Examples/Nodify.Calculator/EditorView.xaml b/Examples/Nodify.Calculator/EditorView.xaml
index 9afd566..da80708 100644
--- a/Examples/Nodify.Calculator/EditorView.xaml
+++ b/Examples/Nodify.Calculator/EditorView.xaml
@@ -483,6 +483,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
node is SystemOperationViewModel sys && sys.SystemOperationType == SystemOperations.PARSEJSON;
+ => node is ParseJsonOperationViewModel
+ || (node is SystemOperationViewModel sys && sys.SystemOperationType == SystemOperations.PARSEJSON);
public string Resolve(OperationViewModel node, ConnectorViewModel outputConnector, IValueResolutionContext ctx)
{
var input = ResolverUtils.FirstDataInput(node);
- return input == null ? null : ctx.Read(input);
+ if (input == null) return null;
+
+ var raw = ctx.Read(input);
+ if (string.IsNullOrEmpty(raw)) return null;
+
+ // Ensure we have valid JSON — if not, serialize the raw value
+ var trimmed = raw.Trim();
+ bool isJson = (trimmed.StartsWith("{") && trimmed.EndsWith("}"))
+ || (trimmed.StartsWith("[") && trimmed.EndsWith("]"))
+ || (trimmed.StartsWith("\"") && trimmed.EndsWith("\""));
+ if (!isJson)
+ {
+ try
+ {
+ raw = Newtonsoft.Json.JsonConvert.SerializeObject(raw);
+ }
+ catch { /* keep raw as-is */ }
+ }
+
+ // Parse and re-serialize to ensure clean JSON output
+ try
+ {
+ var parsed = Newtonsoft.Json.JsonConvert.DeserializeObject(raw);
+ return Newtonsoft.Json.JsonConvert.SerializeObject(parsed);
+ }
+ catch
+ {
+ return raw;
+ }
}
}
diff --git a/Examples/Nodify.Calculator/Models/SaveGraphModel.cs b/Examples/Nodify.Calculator/Models/SaveGraphModel.cs
index 68f1de0..e4a01bd 100644
--- a/Examples/Nodify.Calculator/Models/SaveGraphModel.cs
+++ b/Examples/Nodify.Calculator/Models/SaveGraphModel.cs
@@ -38,6 +38,9 @@ namespace Nodify.Calculator.Models
public string AuthBaseUrl { get; set; } = string.Empty;
public string AuthType { get; set; } = string.Empty;
+ // Parse Json node properties
+ public string ParseJsonTargetType { get; set; } = string.Empty;
+
// Take node properties
public int NthIndex { get; set; }
public bool IsRandom { get; set; }
diff --git a/Examples/Nodify.Calculator/NodeHandlers/NodeHandlerRegistry.cs b/Examples/Nodify.Calculator/NodeHandlers/NodeHandlerRegistry.cs
index 86943f7..ea74eb8 100644
--- a/Examples/Nodify.Calculator/NodeHandlers/NodeHandlerRegistry.cs
+++ b/Examples/Nodify.Calculator/NodeHandlers/NodeHandlerRegistry.cs
@@ -42,6 +42,7 @@ namespace Nodify.Calculator.NodeHandlers
_handlers.Add(new NewObjectHandler());
_handlers.Add(new CopyHandler());
_handlers.Add(new SplitHandler());
+ _handlers.Add(new ParseJsonHandler());
_handlers.Add(new GetSetVariableHandler());
_handlers.Add(new GetSetModelHandler());
_handlers.Add(new BeginEndHandler());
@@ -81,6 +82,7 @@ namespace Nodify.Calculator.NodeHandlers
{
KnotOperationViewModel => _handlers.OfType().FirstOrDefault(),
StringConcatOperationViewModel => _handlers.OfType().FirstOrDefault(),
+ ParseJsonOperationViewModel => _handlers.OfType().FirstOrDefault(),
FunctionOperationViewModel => _handlers.OfType().FirstOrDefault(),
AuthOperationViewModel => _handlers.OfType().FirstOrDefault(),
TakeOperationViewModel => _handlers.OfType().FirstOrDefault(),
diff --git a/Examples/Nodify.Calculator/NodeHandlers/ParseJsonHandler.cs b/Examples/Nodify.Calculator/NodeHandlers/ParseJsonHandler.cs
new file mode 100644
index 0000000..631b2c2
--- /dev/null
+++ b/Examples/Nodify.Calculator/NodeHandlers/ParseJsonHandler.cs
@@ -0,0 +1,97 @@
+using Nodify.Calculator.Models;
+using System.Drawing;
+
+namespace Nodify.Calculator.NodeHandlers
+{
+ public class ParseJsonHandler : INodeHandler
+ {
+ public string NodeTypeKey => "System";
+
+ public bool CanCreate(OperationInfoViewModel info)
+ => info.Type == OperationType.System && info.sysOp == SystemOperations.PARSEJSON;
+
+ public bool CanRestore(NodeData data)
+ => data.NodeType == "System" && data.SystemOp == nameof(SystemOperations.PARSEJSON);
+
+ public OperationViewModel Create(OperationInfoViewModel info)
+ {
+ var op = new ParseJsonOperationViewModel();
+
+ // Input: universal connector that accepts any shape (Circle, Square, Grid)
+ op.Input.Add(new ConnectorViewModel
+ {
+ Title = "Input",
+ Shape = ConnectorShape.Circle,
+ ConnectorColor = Color.White,
+ IsCopyConnector = true
+ });
+
+ // Default output (will be updated when user selects target type)
+ op.Output.Add(new ConnectorViewModel
+ {
+ Title = "Result",
+ IsInput = false,
+ Shape = ConnectorShape.Circle,
+ ConnectorColor = Color.LimeGreen,
+ DataType = "object"
+ });
+
+ return op;
+ }
+
+ public OperationViewModel Restore(NodeData data)
+ {
+ var op = new ParseJsonOperationViewModel();
+
+ // Input: universal connector
+ op.Input.Add(new ConnectorViewModel
+ {
+ Title = "Input",
+ Shape = ConnectorShape.Circle,
+ ConnectorColor = Color.White,
+ IsCopyConnector = true
+ });
+
+ // Restore adapted input connector from saved data
+ if (data.InputConnectors.Count > 0 && data.InputConnectors[0].Shape != "Circle")
+ {
+ var saved = data.InputConnectors[0];
+ var inp = op.Input[0];
+ inp.Shape = NodeHandlerRegistry.ParseShape(saved.Shape);
+ inp.ConnectorColor = Color.FromArgb(saved.ColorArgb);
+ inp.DataType = saved.DataType;
+ }
+
+ // Restore selected target type (this triggers UpdateOutputConnector via property setter)
+ if (!string.IsNullOrEmpty(data.ParseJsonTargetType))
+ {
+ op.RefreshAvailableTypes();
+ op.SelectedTargetType = data.ParseJsonTargetType;
+ }
+ else
+ {
+ // Default output
+ op.Output.Add(new ConnectorViewModel
+ {
+ Title = "Result",
+ IsInput = false,
+ Shape = ConnectorShape.Circle,
+ ConnectorColor = Color.LimeGreen,
+ DataType = "object"
+ });
+ }
+
+ return op;
+ }
+
+ public void Save(OperationViewModel vm, NodeData data)
+ {
+ data.NodeType = "System";
+ data.SystemOp = nameof(SystemOperations.PARSEJSON);
+ if (vm is ParseJsonOperationViewModel parseVm)
+ {
+ data.ParseJsonTargetType = parseVm.SelectedTargetType ?? "object";
+ }
+ }
+ }
+}
diff --git a/Examples/Nodify.Calculator/ParseJsonOperationViewModel.cs b/Examples/Nodify.Calculator/ParseJsonOperationViewModel.cs
new file mode 100644
index 0000000..9e0cbc3
--- /dev/null
+++ b/Examples/Nodify.Calculator/ParseJsonOperationViewModel.cs
@@ -0,0 +1,103 @@
+using System.Collections.ObjectModel;
+using System.Drawing;
+using System.IO;
+using System.Linq;
+
+namespace Nodify.Calculator
+{
+ public class ParseJsonOperationViewModel : SystemOperationViewModel
+ {
+ private string _selectedTargetType = "object";
+
+ public ObservableCollection AvailableTargetTypes { get; } = new ObservableCollection();
+
+ public string SelectedTargetType
+ {
+ get => _selectedTargetType;
+ set
+ {
+ if (SetProperty(ref _selectedTargetType, value))
+ {
+ UpdateOutputConnector();
+ }
+ }
+ }
+
+ public ParseJsonOperationViewModel()
+ {
+ Title = "Parse Json";
+ SystemOperationType = SystemOperations.PARSEJSON;
+ RefreshAvailableTypes();
+ }
+
+ public void RefreshAvailableTypes()
+ {
+ var current = _selectedTargetType;
+ AvailableTargetTypes.Clear();
+ AvailableTargetTypes.Add("object");
+
+ var customModelDir = Path.Combine(ProjectManager.ProjectDirectory, "CustomModels");
+ if (Directory.Exists(customModelDir))
+ {
+ var modelFiles = Directory.GetFiles(customModelDir, "*.cs");
+ foreach (var file in modelFiles)
+ {
+ var name = Path.GetFileNameWithoutExtension(file);
+ AvailableTargetTypes.Add(name);
+ AvailableTargetTypes.Add($"List<{name}>");
+ }
+ }
+
+ // Restore selection if it still exists
+ if (!string.IsNullOrEmpty(current) && AvailableTargetTypes.Contains(current))
+ _selectedTargetType = current;
+ else
+ _selectedTargetType = "object";
+ }
+
+ private void UpdateOutputConnector()
+ {
+ // Remove existing non-flow outputs
+ var toRemove = Output.Where(o => o.Shape != ConnectorShape.Triangle).ToList();
+ foreach (var o in toRemove)
+ Output.Remove(o);
+
+ var targetType = _selectedTargetType ?? "object";
+
+ if (targetType == "object")
+ {
+ Output.Add(new ConnectorViewModel
+ {
+ Title = "Result",
+ IsInput = false,
+ Shape = ConnectorShape.Circle,
+ ConnectorColor = Color.LimeGreen,
+ DataType = "object"
+ });
+ }
+ else if (targetType.StartsWith("List<") && targetType.EndsWith(">"))
+ {
+ Output.Add(new ConnectorViewModel
+ {
+ Title = targetType,
+ IsInput = false,
+ Shape = ConnectorShape.Grid,
+ ConnectorColor = Color.MediumSpringGreen,
+ DataType = targetType
+ });
+ }
+ else
+ {
+ // Model type
+ Output.Add(new ConnectorViewModel
+ {
+ Title = targetType,
+ IsInput = false,
+ Shape = ConnectorShape.Square,
+ ConnectorColor = Color.MediumPurple,
+ DataType = targetType
+ });
+ }
+ }
+ }
+}