diff --git a/Examples/Nodify.Calculator/ConnectorViewModel.cs b/Examples/Nodify.Calculator/ConnectorViewModel.cs
index 32899c8..33f5444 100644
--- a/Examples/Nodify.Calculator/ConnectorViewModel.cs
+++ b/Examples/Nodify.Calculator/ConnectorViewModel.cs
@@ -31,6 +31,19 @@ namespace Nodify.Calculator
.Then(() => ValueObservers.ForEach(o => o.Value = value));
}
+ private string _userInput = string.Empty;
+ ///
+ /// Free-form text the user types directly into the connector's input textbox
+ /// (visible only when the connector is not connected). Used by handlers like
+ /// NEW_OBJECT to read non-numeric values (strings, bools, JSON snippets, ...)
+ /// that (a double) cannot represent.
+ ///
+ public string UserInput
+ {
+ get => _userInput;
+ set => SetProperty(ref _userInput, value);
+ }
+
private ConnectorShape _shape;
public ConnectorShape Shape
{
diff --git a/Examples/Nodify.Calculator/EditorView.xaml b/Examples/Nodify.Calculator/EditorView.xaml
index e4b68d8..6b31234 100644
--- a/Examples/Nodify.Calculator/EditorView.xaml
+++ b/Examples/Nodify.Calculator/EditorView.xaml
@@ -273,7 +273,8 @@
-
diff --git a/Examples/Nodify.Calculator/Execution/Handlers/NodeHandlers.cs b/Examples/Nodify.Calculator/Execution/Handlers/NodeHandlers.cs
index c38ba22..35384c0 100644
--- a/Examples/Nodify.Calculator/Execution/Handlers/NodeHandlers.cs
+++ b/Examples/Nodify.Calculator/Execution/Handlers/NodeHandlers.cs
@@ -95,10 +95,23 @@ namespace Nodify.Calculator.Execution.Handlers
var propPath = parenIdx > 0 ? title.Substring(0, parenIdx).Trim() : title.Trim();
if (string.IsNullOrEmpty(propPath)) continue;
- var val = ctx.ReadInput(inp);
+ // If a wire is connected → read from upstream node.
+ // Otherwise fall back to whatever the user typed in the inline TextBox (UserInput).
+ // If neither is provided, skip the field so the JSON only carries explicit values.
+ string val;
+ if (inp.IsConnected)
+ {
+ val = ctx.ReadInput(inp);
+ }
+ else
+ {
+ if (string.IsNullOrWhiteSpace(inp.UserInput)) continue;
+ val = inp.UserInput;
+ }
+
ctx.Log($"[NEW OBJECT] {propPath} = {(val?.Length > 80 ? val.Substring(0, 80) + "..." : val ?? "null")}");
- SetNestedValue(root, propPath, val);
+ SetNestedValue(root, propPath, val, inp.DataType);
}
var json = root.ToString(Formatting.None);
@@ -109,9 +122,10 @@ namespace Nodify.Calculator.Execution.Handlers
///
/// Sets a value at a dot-notation path in a JObject, creating intermediate objects as needed.
- /// e.g., "Address.Street" with value "Main St" → { "Address": { "Street": "Main St" } }
+ /// The leaf token is coerced according to so that string fields
+ /// remain JSON strings, numeric fields remain JSON numbers, etc.
///
- private static void SetNestedValue(JObject root, string path, string val)
+ private static void SetNestedValue(JObject root, string path, string val, string dataType)
{
var parts = path.Split('.');
var current = root;
@@ -129,14 +143,56 @@ namespace Nodify.Calculator.Execution.Handlers
}
var leaf = parts[parts.Length - 1];
- if (val != null)
+ current[leaf] = CoerceValue(val, dataType);
+ }
+
+ private static JToken CoerceValue(string val, string dataType)
+ {
+ if (val == null) return JValue.CreateNull();
+
+ var dt = (dataType ?? "").Trim().ToLowerInvariant();
+
+ // Lists / nested objects / unspecified — try JSON parse first, fall back to string.
+ if (dt.StartsWith("list<") || dt == "object" || string.IsNullOrEmpty(dt))
{
- try { current[leaf] = JToken.Parse(val); }
- catch { current[leaf] = val; }
+ try { return JToken.Parse(val); }
+ catch { return new JValue(val); }
}
- else
+
+ switch (dt)
{
- current[leaf] = null;
+ case "string":
+ case "char":
+ case "guid":
+ case "datetime":
+ return new JValue(val);
+
+ case "bool":
+ case "boolean":
+ if (bool.TryParse(val, out var b)) return new JValue(b);
+ return new JValue(val);
+
+ case "int":
+ case "long":
+ case "short":
+ case "byte":
+ if (long.TryParse(val, System.Globalization.NumberStyles.Any,
+ System.Globalization.CultureInfo.InvariantCulture, out var l))
+ return new JValue(l);
+ return new JValue(val);
+
+ case "double":
+ case "float":
+ case "decimal":
+ if (double.TryParse(val, System.Globalization.NumberStyles.Any,
+ System.Globalization.CultureInfo.InvariantCulture, out var d))
+ return new JValue(d);
+ return new JValue(val);
+
+ default:
+ // Custom model type referenced by name → expect a JSON object string.
+ try { return JToken.Parse(val); }
+ catch { return new JValue(val); }
}
}
}