Skip to Content

Json

The std Json module converts between JSON and language values in both directions: strongly typed Parse / Write, the dynamic ParseAny, and the Stringify serializer.

Strongly Typed Parsing

Json.Parse<T>(s) parses JSON text into the target type; failure goes through fail Error (error set Syntax | MissingField | TypeMismatch):

struct Actor { public var name: string = ""; public var hp: int = 0; } func main() -> int { val text = r#"{"name": "a", "hp": 7}"#; print(try? Json.Parse<Actor>(text) == null); val good = try? Json.Parse<Actor>(text); print(good?.name, good?.hp); val captured = capture Json.Parse<Actor>(text); match captured { Success(v) => print(v.name), Failure(e) => print("failed"), } return 0; }
False a 7 a

The root type can be a scalar, List, Set, Map, or named struct / class; enum roots are not supported. A JSON null assigned to a non-nullable field always fails with TypeMismatch — null never slips through silently.

Failure-Free Consumption

Json.Parse<T> is a fallible form; use try? to dodge the failure, or capture to collect it into a value and branch on it with recover; see failure:

val text = r#"{"name": "a", "hp": 7}"#; val ok = try? Json.Parse<Actor>(text); print(ok != null); val bad = try? Json.Parse<Actor>("{nope"); print(bad == null);
True True

Serialization

val text = r#"{"name": "a", "hp": 7}"#; val good = try? Json.Parse<Actor>(text); val serialized = try? Json.Stringify(good); print(serialized); print(try? Json.StringifyPretty([1, 2], 2));
{"name":"a","hp":7} [ 1, 2 ]

Json.Stringify(v) emits compact JSON and escapes non-ASCII characters as \uXXXX; StringifyPretty(v, indent) indents and breaks lines.

ParseAny: The Dynamic Surface

When the type is only known at runtime, Json.ParseAny(s) fail Error -> any parses into any, and you narrow afterwards with is (see Any):

val text = r#"{"name": "a", "hp": 7}"#; val anyv = try? Json.ParseAny(text); print(anyv == null);
False

Container Roots and Error Arms

The root type also accepts Map (scalar keys) and Set; on parse failure, capture plus match gets you the error value itself, and ToString() includes the module qualification and payload:

val m = try? Json.Parse<map<string, int>>("{\"a\": 1, \"b\": 2}"); print(m?.Count ?? -1); print(m?.Keys() ?? new list<string>()); val bad = capture Json.Parse<int>("{\"x\":}"); match bad { Success(v) => print(v), Failure(e) => print("parse failed", e), }
2 [a, b] parse failed Json.Syntax(pos=5)

Each member of the error set Syntax | MissingField | TypeMismatch carries a payload (Syntax carries the offending position). Matching arms by error type, as in Failure(Syntax(p)) => ..., splits failures precisely.

Notes

  • Json lives in the std::Json module; the Json. prefix works without an import.
  • A Map with scalar keys joins conversions directly, and parsing maps a JSON object onto the fields of a named type by field name.
  • DateTime does not enter the Json walker; persist it through a UnixMicros integer field.
Last updated on October 11, 2026