JsonScalar
Namespace: Meshmakers.Octo.Runtime.Contracts.Serialization
Converts a scalar JSON value to its Newtonsoft-parity CLR boxing — the single source of the rules previously hand-rolled across many pipeline nodes and inside RtAttributesConverter. Integers that fit in Int32 box to Int32, larger integers to Int64, reals to Double, ISO-8601 strings to DateTime (when requested), bools to Boolean; objects/arrays return null (callers navigate those structurally).
public static class JsonScalar
Inheritance Object → JsonScalar
Remarks:
The boxing rules are verified empirically against Newtonsoft by
Sdk.Common.PipelineParityTests.AttributeRoundTripClrTypeParityTests. That suite is
the authoritative contract — any divergence between these rules and what Newtonsoft's
JObject.FromObject / JToken.ToObject in-memory round-trip produces is a
regression. Irreducible divergences (float vs double, decimal vs double, DateTimeOffset
vs DateTime — lost because JSON has no source-CLR-type marker for those) are listed in
AttributeValueParityCorpus.IrreducibleDivergences.
Methods
ToClr(JsonElement, Boolean)
Newtonsoft-parity scalar boxing of .
public static object ToClr(JsonElement element, bool parseDateStrings)
Parameters
element JsonElement
parseDateStrings Boolean
Returns
ToClr(JsonValue, Boolean)
Newtonsoft-parity scalar boxing of a .
public static object ToClr(JsonValue value, bool parseDateStrings)
Parameters
value JsonValue
parseDateStrings Boolean
Returns
Remarks:
A is either element-backed (parsed JSON / Deserialize<JsonNode>
/ SerializeToNode of a boxed primitive) or CLR-backed (JsonValue.Create(primitive)
and its DeepClone — what pipeline nodes produce when they author a scalar, e.g.
TransformStringNode's Set(path, JsonValue.Create(result)), surviving the overlay
store and detach). Only the element-backed form can be read via GetValue<JsonElement>();
a CLR-backed value throws InvalidOperationException there. So element-backed values
route through the proven JsonScalar.ToClr(JsonElement, Boolean), and CLR-backed values are unwrapped
by kind with the SAME Newtonsoft-parity boxing (string→string, ISO-string→DateTime, Int32 then
Int64 then Double, bool→bool). The Number arm tries Int32 before Int64 before Double because STJ's
TryGetValue<T> on a CLR-backed value is exact-type (no numeric coercion): a boxed
double 2.0 fails TryGetValue<int> and stays a Double rather than
collapsing to Int32 (the Int32-vs-double regression the parity converters guard against).
TryToNumber<T>(JsonNode, out T)
public static bool TryToNumber<T>(JsonNode node, out T value)
Type Parameters
T
Parameters
node JsonNode
value T
Returns
TryToDouble(JsonNode, out Double)
Reads as a Double — the non-generic counterpart of
TryToNumber<T>. Accepts JSON numbers natively and parses numeric JSON strings
under invariant culture; returns false otherwise. Unlike the generic overload (which needs
IParsable<T>, net7+), this is available on every target framework — including
netstandard2.0 — so callers that cannot use the generic still get the shared parity rules.
public static bool TryToDouble(JsonNode node, out Double value)
Parameters
node JsonNode
value Double