Implemente parse json node having universal connector
Some checks failed
Build / build (push) Has been cancelled

This commit is contained in:
Ankitkumar Satapara
2026-04-22 21:21:00 +05:30
parent 132f6068b5
commit c8c556921f
7 changed files with 308 additions and 4 deletions

View File

@@ -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;
}
}
}

View File

@@ -483,6 +483,25 @@
</nodify:Node>
</DataTemplate>
<DataTemplate DataType="{x:Type local:ParseJsonOperationViewModel}">
<nodify:Node Header="{Binding Title}"
Content="{Binding}"
Input="{Binding Input}"
Output="{Binding Output}">
<nodify:Node.ContentTemplate>
<DataTemplate DataType="{x:Type local:ParseJsonOperationViewModel}">
<StackPanel>
<TextBlock Text="Target Type" FontSize="10" Foreground="Gray" Margin="0,0,0,2" />
<ComboBox ItemsSource="{Binding AvailableTargetTypes}"
SelectedItem="{Binding SelectedTargetType, Mode=TwoWay}"
MinWidth="120"
Margin="0,0,0,2" />
</StackPanel>
</DataTemplate>
</nodify:Node.ContentTemplate>
</nodify:Node>
</DataTemplate>
<DataTemplate DataType="{x:Type local:StringConcatOperationViewModel}">
<nodify:Node Header="{Binding Title}"
Content="{Binding}"

View File

@@ -135,18 +135,48 @@ namespace Nodify.Calculator.Execution.Resolvers
}
// ─────────────────────────────────────────────────────────────────────────────
// PARSEJSON: passthrough (output = parsed form of input). For value-reading
// purposes we just forward the raw JSON downstream.
// PARSEJSON: takes any input, ensures it is valid JSON, then parses it.
// If the input is not valid JSON, it serializes it first, then forwards
// the JSON string downstream for the selected target type.
// ─────────────────────────────────────────────────────────────────────────────
internal sealed class ParseJsonNodeValueResolver : INodeValueResolver
{
public bool CanResolve(OperationViewModel node, ConnectorViewModel c)
=> 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;
}
}
}

View File

@@ -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; }

View File

@@ -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<KnotHandler>().FirstOrDefault(),
StringConcatOperationViewModel => _handlers.OfType<StringConcatHandler>().FirstOrDefault(),
ParseJsonOperationViewModel => _handlers.OfType<ParseJsonHandler>().FirstOrDefault(),
FunctionOperationViewModel => _handlers.OfType<FunctionHandler>().FirstOrDefault(),
AuthOperationViewModel => _handlers.OfType<AuthHandler>().FirstOrDefault(),
TakeOperationViewModel => _handlers.OfType<TakeHandler>().FirstOrDefault(),

View File

@@ -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";
}
}
}
}

View File

@@ -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<string> AvailableTargetTypes { get; } = new ObservableCollection<string>();
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
});
}
}
}
}