Skip to content

Standard Library: JSON

Shape provides a typed JSON module for parsing, navigation, serialization, and direct deserialization into user-defined types with @alias support.

from std::core::json use { parse, stringify, is_valid }

All JSON functions live under the json namespace.

json::parse returns a Result<Json, string> — a typed enum that preserves the structure of the original JSON:

let data = json::parse("{\"name\": \"Alice\", \"age\": 30}")?

The return value is a Json enum (see below), not a raw hashmap. This means every JSON value flowing through your program is fully typed.

When you pass a type as the second argument, json::parse deserializes directly into that struct:

type Trade {
symbol: string,
price: number,
volume: int,
}
let t = json::parse("{\"symbol\": \"ES\", \"price\": 4525.50, \"volume\": 1200}", Trade)?
print(t.symbol) // "ES"
print(t.price) // 4525.5

Fields are matched by name. Nested structs are recursively deserialized if their schemas are registered.

When JSON keys don’t match Shape field names, use @alias to map between the wire name and the Shape field:

type Trade {
@alias("Close Price")
close: number,
@alias("vol.")
volume: number,
}
let t = json::parse("{\"Close Price\": 100.5, \"vol.\": 1000}", Trade)?
print(t.close) // 100.5
print(t.volume) // 1000

This is especially useful for external data sources with spaces, dots, or other characters that aren’t valid Shape identifiers. @alias works consistently across JSON deserialization, Arrow/DataTable column binding, and Python interop.

json::parse returns a Json enum with seven variants matching the JSON specification:

pub enum Json {
Null,
Bool(bool),
Int(int),
Number(number),
Str(string),
Array(any),
Object(any),
}

Use standard pattern matching to inspect JSON values:

let data = json::parse("{\"name\": \"Alice\"}")?
match data {
Json::Object(obj) => print("got an object"),
Json::Array(arr) => print("got an array"),
Json::Str(s) => print(f"got string: {s}"),
Json::Int(i) => print(f"got int: {i}"),
Json::Number(n) => print(f"got number: {n}"),
Json::Bool(b) => print(f"got bool: {b}"),
Json::Null => print("got null"),
}

The Json type has navigation methods for exploring nested structures without matching at every level:

MethodSignatureDescription
getget(key: string) -> JsonLook up a key in an object. Returns Json::Null for non-objects or missing keys.
atat(index: number) -> JsonAccess an array element by index. Returns Json::Null for non-arrays or out-of-range.
is_nullis_null() -> boolCheck if the value is Json::Null.
keyskeys() -> Array<string>Return the keys of an object, or an empty array.
lenlen() -> numberLength of an array or object, or 0.

Example — navigating nested JSON:

let data = json::parse("{\"users\": [{\"name\": \"Alice\"}, {\"name\": \"Bob\"}]}")?
let first_name = data.get("users").at(0).get("name")
// first_name is Json::Str("Alice")
let count = data.get("users").len()
// count is 2

Navigation never throws. Missing paths return Json::Null, which propagates through further navigation calls:

let data = json::parse("{\"a\": 1}")?
let missing = data.get("b").get("c").get("d")
// missing is Json::Null
assert(missing.is_null())

The Json type implements TryFrom<Json> for string, int, number, and bool. These auto-derive TryInto so the as? operator works directly:

let data = json::parse("{\"name\": \"Alice\", \"score\": 95, \"active\": true}")?
let name = (data.get("name") as? string)?
// name is "Alice"
let score = (data.get("score") as? number)?
// score is 95.0
let active = (data.get("active") as? bool)?
// active is true

If the JSON value doesn’t match the target type, as? returns an Err:

let data = json::parse("{\"name\": \"Alice\"}")?
let result = data.get("name") as? number
// result is Err("Json value is not a number")

Convert any Shape value to a JSON string. The pretty flag is required and controls indentation:

let text = json::stringify(value, false)? // compact
let pretty = json::stringify(value, true)? // pretty-printed

Check whether a string is syntactically valid JSON without parsing it:

let ok = json::is_valid("{\"a\": 1}") // true
let bad = json::is_valid("{invalid}") // false

Loading external data with field aliasing and typed extraction:

from std::core::json use { parse, stringify }
from std::core::io use { open, read_to_string }
type StockQuote {
@alias("Global Quote")
quote: QuoteData,
}
type QuoteData {
@alias("01. symbol")
symbol: string,
@alias("05. price")
price: number,
@alias("06. volume")
volume: number,
}
let text = io::read_to_string(io::open("quote.json"))
let quote = json::parse(text, StockQuote)?
print(f"{quote.quote.symbol}: ${quote.quote.price}")

Navigating unknown JSON and converting values on the fly:

from std::core::json use { parse }
let data = json::parse("{\"results\": [{\"name\": \"Alice\", \"score\": 95}]}")?
for i in 0..data.get("results").len() {
let entry = data.get("results").at(i)
let name = (entry.get("name") as? string)?
let score = (entry.get("score") as? number)?
print(f"{name}: {score}")
}
FunctionSignatureDescription
json::parse(text)(string) -> Result<Json, string>Parse JSON into a Json enum
json::parse(text, Type)(string, Type) -> Result<Type, string>Deserialize JSON into a typed struct
json::stringify(value, pretty)(any, bool) -> Result<string, string>Serialize a value to JSON; pretty: true indents output
json::is_valid(text)(string) -> boolCheck if a string is valid JSON