diff --git a/Examples/Nodify.Calculator/Execution/Resolvers/NodeValueResolvers.cs b/Examples/Nodify.Calculator/Execution/Resolvers/NodeValueResolvers.cs
index 13a8411..6a2196f 100644
--- a/Examples/Nodify.Calculator/Execution/Resolvers/NodeValueResolvers.cs
+++ b/Examples/Nodify.Calculator/Execution/Resolvers/NodeValueResolvers.cs
@@ -175,31 +175,169 @@ namespace Nodify.Calculator.Execution.Resolvers
var raw = ctx.Read(input);
if (string.IsNullOrEmpty(raw)) return null;
- // Ensure we have valid JSON — if not, serialize the raw value
+ ctx.Log($"[PARSE JSON] Raw input: {(raw.Length > 120 ? raw.Substring(0, 120) + "..." : raw)}");
+
+ // Ensure we have valid JSON
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);
- }
+ try { raw = Newtonsoft.Json.JsonConvert.SerializeObject(raw); }
catch { /* keep raw as-is */ }
}
- // Parse and re-serialize to ensure clean JSON output
+ var targetType = (node is ParseJsonOperationViewModel pj) ? pj.SelectedTargetType : "object";
+ ctx.Log($"[PARSE JSON] Target type: {targetType}");
+
try
{
- var parsed = Newtonsoft.Json.JsonConvert.DeserializeObject(raw);
- return Newtonsoft.Json.JsonConvert.SerializeObject(parsed);
+ var token = JToken.Parse(raw);
+
+ if (targetType == "object")
+ return token.ToString(Formatting.None);
+
+ 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($"[PARSE JSON] ERROR: Could not find an array in the input to convert to {targetType}.", logType.Error);
+ return null;
+ }
+
+ if (modelProps != null && modelProps.Length > 0)
+ {
+ var projected = new JArray();
+ foreach (var item in array)
+ {
+ var projectedItem = ProjectToModel(item, modelProps);
+ if (projectedItem != null)
+ projected.Add(projectedItem);
+ else
+ ctx.Log($"[PARSE JSON] WARNING: An array item could not be projected to {modelName}, skipping.", logType.Warning);
+ }
+ ctx.Log($"[PARSE JSON] Parsed {projected.Count} items as List<{modelName}>.");
+ return projected.ToString(Formatting.None);
+ }
+
+ ctx.Log($"[PARSE JSON] Model file for \"{modelName}\" not found, returning raw array.");
+ return array.ToString(Formatting.None);
+ }
+ else
+ {
+ // Single model type
+ var modelProps = LoadModelProperties(targetType, ctx);
+ var obj = FindObject(token);
+ if (obj == null)
+ {
+ ctx.Log($"[PARSE JSON] ERROR: Could not find an object in the input to convert to {targetType}.", logType.Error);
+ return null;
+ }
+
+ if (modelProps != null && modelProps.Length > 0)
+ {
+ var projected = ProjectToModel(obj, modelProps);
+ if (projected == null)
+ {
+ ctx.Log($"[PARSE JSON] ERROR: Failed to project object to {targetType}.", logType.Error);
+ return null;
+ }
+ ctx.Log($"[PARSE JSON] Parsed object as {targetType} with {projected.Count} properties.");
+ return projected.ToString(Formatting.None);
+ }
+
+ ctx.Log($"[PARSE JSON] Model file for \"{targetType}\" not found, returning raw object.");
+ return obj.ToString(Formatting.None);
+ }
}
- catch
+ catch (System.Exception ex)
{
- return raw;
+ ctx.Log($"[PARSE JSON] ERROR: {ex.Message}", logType.Error);
+ return null;
}
}
+
+ ///
+ /// Reads property names from a model .cs file in CustomModels directory.
+ ///
+ private static string[] LoadModelProperties(string modelName, IValueResolutionContext ctx)
+ {
+ var modelPath = System.IO.Path.Combine(ProjectManager.ProjectDirectory, "CustomModels", modelName + ".cs");
+ if (!System.IO.File.Exists(modelPath)) return null;
+
+ var lines = System.IO.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($"[PARSE JSON] Loaded model \"{modelName}\" with properties: {string.Join(", ", props)}");
+ return props;
+ }
+
+ ///
+ /// Projects a JSON token to a JObject containing only the specified model properties (case-insensitive).
+ /// Unwraps a "data" envelope if needed.
+ ///
+ private static JObject ProjectToModel(JToken token, string[] modelProps)
+ {
+ var source = token as JObject;
+ if (source == null) return null;
+
+ // If the object has a single wrapper property with an inner object, unwrap it
+ 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;
+ }
}
// ─────────────────────────────────────────────────────────────────────────────
diff --git a/Examples/Nodify.Calculator/Executor.cs b/Examples/Nodify.Calculator/Executor.cs
index 4184cce..c68a3d0 100644
--- a/Examples/Nodify.Calculator/Executor.cs
+++ b/Examples/Nodify.Calculator/Executor.cs
@@ -230,6 +230,9 @@ namespace Nodify.Calculator
if (!string.IsNullOrEmpty(resolved))
{
OnLogMe?.Invoke($"[RESOLVE] Resolver returned: {(resolved.Length > 80 ? resolved.Substring(0, 80) + "..." : resolved)}");
+ // Cache so subsequent reads hit the fast path
+ if (srcNodeId != null)
+ outputs[srcNodeId] = resolved;
return resolved;
}
OnLogMe?.Invoke($"[RESOLVE] Resolver returned null/empty, trying next...", logType.Warning);
@@ -246,6 +249,8 @@ namespace Nodify.Calculator
if (!string.IsNullOrEmpty(passthrough))
{
OnLogMe?.Invoke($"[RESOLVE] Passthrough returned: {(passthrough.Length > 80 ? passthrough.Substring(0, 80) + "..." : passthrough)}");
+ if (srcNodeId != null)
+ outputs[srcNodeId] = passthrough;
return passthrough;
}
}