Added deserialzor node for parsing the json to model ot list of model
Some checks failed
Build / build (push) Successful in 28s
CodeQL / Analyze (csharp) (push) Has been cancelled

This commit is contained in:
Ankitkumar Satapara
2026-04-25 00:37:53 +05:30
parent d4e6a7a06f
commit 77cd472f73
8 changed files with 450 additions and 1 deletions

View File

@@ -0,0 +1,100 @@
using System.Collections.ObjectModel;
using System.Drawing;
using System.IO;
using System.Linq;
namespace Nodify.Calculator
{
public class DeserializeOperationViewModel : 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 DeserializeOperationViewModel()
{
Title = "Deserialize";
SystemOperationType = SystemOperations.DESERIALIZE;
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}>");
}
}
if (!string.IsNullOrEmpty(current) && AvailableTargetTypes.Contains(current))
_selectedTargetType = current;
else
_selectedTargetType = "object";
}
private void UpdateOutputConnector()
{
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 = NodeColors.List,
DataType = targetType
});
}
else
{
Output.Add(new ConnectorViewModel
{
Title = targetType,
IsInput = false,
Shape = ConnectorShape.Square,
ConnectorColor = NodeColors.Model,
DataType = targetType
});
}
}
}
}

View File

@@ -502,6 +502,25 @@
</nodify:Node> </nodify:Node>
</DataTemplate> </DataTemplate>
<DataTemplate DataType="{x:Type local:DeserializeOperationViewModel}">
<nodify:Node Header="{Binding Title}"
Content="{Binding}"
Input="{Binding Input}"
Output="{Binding Output}">
<nodify:Node.ContentTemplate>
<DataTemplate DataType="{x:Type local:DeserializeOperationViewModel}">
<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}"> <DataTemplate DataType="{x:Type local:StringConcatOperationViewModel}">
<nodify:Node Header="{Binding Title}" <nodify:Node Header="{Binding Title}"
Content="{Binding}" Content="{Binding}"

View File

@@ -142,6 +142,191 @@ namespace Nodify.Calculator.Execution.Handlers
} }
} }
internal sealed class DeserializeHandler : INodeExecutionHandler
{
public bool CanHandle(OperationViewModel node, ExecutionContext ctx)
=> node is DeserializeOperationViewModel;
public void Execute(OperationViewModel node, ExecutionContext ctx)
{
var desVm = (DeserializeOperationViewModel)node;
var targetType = desVm.SelectedTargetType ?? "object";
// Read the Json input (first non-Triangle input)
var jsonInput = node.Input.FirstOrDefault(i => i.Shape != ConnectorShape.Triangle);
var raw = ctx.ReadInput(jsonInput) ?? "";
ctx.Log($"[DESERIALIZE] Input: {(raw.Length > 100 ? raw.Substring(0, 100) + "..." : raw)}");
ctx.Log($"[DESERIALIZE] Target type: {targetType}");
if (string.IsNullOrWhiteSpace(raw))
{
ctx.Log("[DESERIALIZE] ERROR: No JSON input provided.", logType.Error);
return;
}
// Ensure valid JSON
var trimmed = raw.Trim();
bool isJson = (trimmed.StartsWith("{") && trimmed.EndsWith("}"))
|| (trimmed.StartsWith("[") && trimmed.EndsWith("]"))
|| (trimmed.StartsWith("\"") && trimmed.EndsWith("\""));
if (!isJson)
{
try { raw = JsonConvert.SerializeObject(raw); }
catch { /* keep raw */ }
}
try
{
var token = JToken.Parse(raw);
if (targetType == "object")
{
var result = token.ToString(Formatting.None);
ctx.Outputs[node.NodeId] = result;
ctx.Log($"[DESERIALIZE] Output (object): {(result.Length > 100 ? result.Substring(0, 100) + "..." : result)}");
return;
}
if (targetType.StartsWith("List<") && targetType.EndsWith(">"))
{
var modelName = targetType.Substring(5, targetType.Length - 6);
var modelProps = LoadModelProperties(modelName, ctx);
var array = FindArray(token);
if (array == null)
{
ctx.Log($"[DESERIALIZE] ERROR: Could not find an array to convert to {targetType}.", logType.Error);
return;
}
if (modelProps != null && modelProps.Length > 0)
{
var projected = new JArray();
foreach (var item in array)
{
var proj = ProjectToModel(item, modelProps);
if (proj != null) projected.Add(proj);
}
var result = projected.ToString(Formatting.None);
ctx.Outputs[node.NodeId] = result;
ctx.Log($"[DESERIALIZE] Deserialized {projected.Count} items as {targetType}.");
}
else
{
var result = array.ToString(Formatting.None);
ctx.Outputs[node.NodeId] = result;
ctx.Log($"[DESERIALIZE] Model \"{modelName}\" not found, returning raw array ({array.Count} items).");
}
return;
}
// Single model
{
var modelProps = LoadModelProperties(targetType, ctx);
var obj = FindObject(token);
if (obj == null)
{
ctx.Log($"[DESERIALIZE] ERROR: Could not find an object to convert to {targetType}.", logType.Error);
return;
}
if (modelProps != null && modelProps.Length > 0)
{
var projected = ProjectToModel(obj, modelProps);
if (projected == null)
{
ctx.Log($"[DESERIALIZE] ERROR: Failed to project to {targetType}.", logType.Error);
return;
}
var result = projected.ToString(Formatting.None);
ctx.Outputs[node.NodeId] = result;
ctx.Log($"[DESERIALIZE] Deserialized as {targetType} with {projected.Count} properties.");
}
else
{
var result = obj.ToString(Formatting.None);
ctx.Outputs[node.NodeId] = result;
ctx.Log($"[DESERIALIZE] Model \"{targetType}\" not found, returning raw object.");
}
}
}
catch (Exception ex)
{
ctx.Log($"[DESERIALIZE] ERROR: {ex.Message}", logType.Error);
}
}
private static string[] LoadModelProperties(string modelName, ExecutionContext ctx)
{
var modelPath = Path.Combine(ProjectManager.ProjectDirectory, "CustomModels", modelName + ".cs");
if (!File.Exists(modelPath)) return null;
var lines = File.ReadAllLines(modelPath);
var props = lines
.Select(l => l.Trim())
.Where(l => l.StartsWith("public ") && l.Contains("{ get;"))
.Select(l =>
{
var parts = l.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
return parts.Length >= 3 ? parts[2] : null;
})
.Where(p => p != null)
.ToArray();
ctx.Log($"[DESERIALIZE] Loaded model \"{modelName}\": {string.Join(", ", props)}");
return props;
}
private static JObject ProjectToModel(JToken token, string[] modelProps)
{
var source = token as JObject;
if (source == null) return null;
if (source.Count == 1)
{
var first = source.Properties().First();
if (first.Value is JObject inner)
source = inner;
}
var result = new JObject();
foreach (var propName in modelProps)
{
var match = source.Properties()
.FirstOrDefault(p => string.Equals(p.Name, propName, StringComparison.OrdinalIgnoreCase));
result[propName] = match != null ? match.Value.DeepClone() : JValue.CreateNull();
}
return result;
}
private static JArray FindArray(JToken token)
{
if (token is JArray arr) return arr;
if (token is JObject obj)
{
foreach (var prop in obj.Properties())
if (prop.Value is JArray innerArr) return innerArr;
foreach (var prop in obj.Properties())
if (prop.Value is JObject inner)
foreach (var innerProp in inner.Properties())
if (innerProp.Value is JArray deepArr) return deepArr;
}
return null;
}
private static JObject FindObject(JToken token)
{
if (token is JObject obj)
{
var props = obj.Properties().ToList();
if (props.Count == 1 && props[0].Value is JObject inner)
return inner;
return obj;
}
return null;
}
}
internal sealed class ForEachHandler : INodeExecutionHandler internal sealed class ForEachHandler : INodeExecutionHandler
{ {
public bool CanHandle(OperationViewModel node, ExecutionContext ctx) public bool CanHandle(OperationViewModel node, ExecutionContext ctx)

View File

@@ -58,6 +58,7 @@ namespace Nodify.Calculator
new NewObjectHandler(), new NewObjectHandler(),
new AssertHandler(), new AssertHandler(),
new ForEachHandler(), new ForEachHandler(),
new Execution.Handlers.DeserializeHandler(),
new FunctionHandler(), new FunctionHandler(),
new ListAddExecutionHandler(), new ListAddExecutionHandler(),
new ListRemoveExecutionHandler(), new ListRemoveExecutionHandler(),

View File

@@ -0,0 +1,130 @@
using Nodify.Calculator.Models;
using System.Drawing;
namespace Nodify.Calculator.NodeHandlers
{
public class DeserializeHandler : INodeHandler
{
public string NodeTypeKey => "System";
public bool CanCreate(OperationInfoViewModel info)
=> info.Type == OperationType.System && info.sysOp == SystemOperations.DESERIALIZE;
public bool CanRestore(NodeData data)
=> data.NodeType == "System" && data.SystemOp == nameof(SystemOperations.DESERIALIZE);
public OperationViewModel Create(OperationInfoViewModel info)
{
var op = new DeserializeOperationViewModel();
// Flow in
op.Input.Add(new ConnectorViewModel
{
Title = "",
Shape = ConnectorShape.Triangle,
ConnectorColor = NodeColors.Exec
});
// String input (accepts any data connection)
op.Input.Add(new ConnectorViewModel
{
Title = "Json",
Shape = ConnectorShape.Circle,
ConnectorColor = NodeColors.String,
IsCopyConnector = true
});
// Flow out
op.Output.Add(new ConnectorViewModel
{
Title = "",
IsInput = false,
Shape = ConnectorShape.Triangle,
ConnectorColor = NodeColors.Exec
});
// 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 DeserializeOperationViewModel();
// Flow in
op.Input.Add(new ConnectorViewModel
{
Title = "",
Shape = ConnectorShape.Triangle,
ConnectorColor = NodeColors.Exec
});
// String input
op.Input.Add(new ConnectorViewModel
{
Title = "Json",
Shape = ConnectorShape.Circle,
ConnectorColor = NodeColors.String,
IsCopyConnector = true
});
// Restore adapted input connector from saved data
if (data.InputConnectors.Count > 1 && data.InputConnectors[1].Shape != "Circle")
{
var saved = data.InputConnectors[1];
var inp = op.Input[1]; // the Json input (index 1, after flow)
inp.Shape = NodeHandlerRegistry.ParseShape(saved.Shape);
inp.ConnectorColor = Color.FromArgb(saved.ColorArgb);
inp.DataType = saved.DataType;
}
// Flow out
op.Output.Add(new ConnectorViewModel
{
Title = "",
IsInput = false,
Shape = ConnectorShape.Triangle,
ConnectorColor = NodeColors.Exec
});
// Restore selected target type
if (!string.IsNullOrEmpty(data.ParseJsonTargetType))
{
op.RefreshAvailableTypes();
op.SelectedTargetType = data.ParseJsonTargetType;
}
else
{
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.DESERIALIZE);
if (vm is DeserializeOperationViewModel desVm)
{
data.ParseJsonTargetType = desVm.SelectedTargetType ?? "object";
}
}
}
}

View File

@@ -43,6 +43,7 @@ namespace Nodify.Calculator.NodeHandlers
_handlers.Add(new CopyHandler()); _handlers.Add(new CopyHandler());
_handlers.Add(new SplitHandler()); _handlers.Add(new SplitHandler());
_handlers.Add(new ParseJsonHandler()); _handlers.Add(new ParseJsonHandler());
_handlers.Add(new DeserializeHandler());
_handlers.Add(new ListAddHandler()); _handlers.Add(new ListAddHandler());
_handlers.Add(new ListRemoveHandler()); _handlers.Add(new ListRemoveHandler());
_handlers.Add(new ListUpdateHandler()); _handlers.Add(new ListUpdateHandler());
@@ -90,6 +91,7 @@ namespace Nodify.Calculator.NodeHandlers
KnotOperationViewModel => _handlers.OfType<KnotHandler>().FirstOrDefault(), KnotOperationViewModel => _handlers.OfType<KnotHandler>().FirstOrDefault(),
StringConcatOperationViewModel => _handlers.OfType<StringConcatHandler>().FirstOrDefault(), StringConcatOperationViewModel => _handlers.OfType<StringConcatHandler>().FirstOrDefault(),
ParseJsonOperationViewModel => _handlers.OfType<ParseJsonHandler>().FirstOrDefault(), ParseJsonOperationViewModel => _handlers.OfType<ParseJsonHandler>().FirstOrDefault(),
DeserializeOperationViewModel => _handlers.OfType<DeserializeHandler>().FirstOrDefault(),
FunctionOperationViewModel => _handlers.OfType<FunctionHandler>().FirstOrDefault(), FunctionOperationViewModel => _handlers.OfType<FunctionHandler>().FirstOrDefault(),
AuthOperationViewModel => _handlers.OfType<AuthHandler>().FirstOrDefault(), AuthOperationViewModel => _handlers.OfType<AuthHandler>().FirstOrDefault(),
TakeOperationViewModel => _handlers.OfType<TakeHandler>().FirstOrDefault(), TakeOperationViewModel => _handlers.OfType<TakeHandler>().FirstOrDefault(),

View File

@@ -144,6 +144,16 @@ namespace Nodify.Calculator
IsFlowNode = false IsFlowNode = false
}; };
var deserializeNode = new OperationInfoViewModel()
{
Title = "Deserialize",
Type = OperationType.System,
sysOp = SystemOperations.DESERIALIZE,
IsFlowNode = true
};
deserializeNode.Input.Add("Json");
deserializeNode.Output.Add("");
systemNodes.Add(authNode); systemNodes.Add(authNode);
systemNodes.Add(copynode); systemNodes.Add(copynode);
systemNodes.Add(debugNode); systemNodes.Add(debugNode);
@@ -155,6 +165,7 @@ namespace Nodify.Calculator
systemNodes.Add(splitNode); systemNodes.Add(splitNode);
systemNodes.Add(assertNode); systemNodes.Add(assertNode);
systemNodes.Add(stringConcatNode); systemNodes.Add(stringConcatNode);
systemNodes.Add(deserializeNode);
return systemNodes; return systemNodes;
} }

View File

@@ -31,7 +31,8 @@ namespace Nodify.Calculator
LIST_COUNT, LIST_COUNT,
LIST_CONTAINS, LIST_CONTAINS,
LIST_CLEAR, LIST_CLEAR,
LIST_GET LIST_GET,
DESERIALIZE
} }
public class SystemOperationViewModel : OperationViewModel public class SystemOperationViewModel : OperationViewModel