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 element.
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)
Reads node as numeric T. Accepts JSON numbers
natively; parses JSON strings under invariant culture. Returns false otherwise.
public static bool TryToNumber<T>(JsonNode node, out T value)
Type Parameters
T
Parameters
node JsonNode
value T
Returns
TryToDouble(JsonNode, out Double)
Reads node 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>), this one has no generic constraint, 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