From 73e725b6ec227495e32110d31fa80a58a9688008 Mon Sep 17 00:00:00 2001 From: Ankitkumar Satapara Date: Sat, 25 Apr 2026 00:19:13 +0530 Subject: [PATCH] Fixed parse json node and added non flow nodes outputs to the output dictionary for storing --- .../Execution/Resolvers/NodeValueResolvers.cs | 158 ++++++++++++++++-- Examples/Nodify.Calculator/Executor.cs | 5 + 2 files changed, 153 insertions(+), 10 deletions(-) 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; } }