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
aThe 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
TrueSerialization
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);FalseContainer 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.
