radlang — JSON (json)

This document covers the json namespace: parsing, serializing, and navigating JSON. It supports both a typed style (parse straight into radlang structs) and a manual style (walk a JsonNode tree by hand).

For language fundamentals see the Overview.


Overview

json is a compiler-intrinsic namespace. The name is reserved and dispatched directly to the runtime — it needs no import:

type Person {
    string name,
    int age
}

fn main() {
    Person p = Person("Ada", 36)
    string encoded = json.from(p)          // {"name":"Ada","age":36}
    Person back = json.parse<Person>(encoded)
    sys.output(back.name)                  // Ada
}

Two built-in types appear here:

  • JsonNode — an opaque handle to a parsed JSON value (object, array, string, number, bool, or null). You never construct it directly; you get one from json.parse.
  • radlang structs and lists, which json.parse<T> and json.from convert to and from JSON automatically.

Serializing — json.from

json.from(value) : string

Converts a radlang value to a JSON string. It handles primitives, null, lists, structs (including nested structs and lists of structs), any, and union types.

type Person {
    string name,
    int age,
    bool is_student
}

fn main() {
    sys.output(json.from(42))         // 42
    sys.output(json.from("hi"))       // "hi"
    sys.output(json.from(true))       // true
    sys.output(json.from([1, 2, 3]))  // [1,2,3]

    Person p = Person("Tim", 30, true)
    sys.output(json.from(p))          // {"name":"Tim","age":30,"is_student":true}
}

Typed parsing — json.parse<T>

json.parse(s: string)     : JsonNode      // untyped: returns a node to navigate
json.parse<T>(s: string)  : T             // typed: deserialize into T

With a type argument, json.parse<T> deserializes directly into a primitive, a struct, or a list — including nested structures. This is the easiest way to consume JSON when you know its shape.

type Person {
    string name,
    int age,
    bool is_student
}

type Group {
    string group_name,
    Person[] members
}

fn main() {
    // Primitives
    int n = json.parse<int>("42")
    bool b = json.parse<bool>("true")

    // Struct
    Person p = json.parse<Person>("{\"name\":\"Ada\",\"age\":36,\"is_student\":false}")
    sys.output(p.name)                 // Ada

    // Nested struct with a list of structs
    string data = json.from(Group("Devs", [Person("Tim", 30, true)]))
    Group g = json.parse<Group>(data)
    sys.output(g.members[0].name)      // Tim
}

Supported type arguments: int, uint, long, float, bool, string, char, byte, any struct type, and lists of those.

Note. Automatic deserialization into a union type is not supported — parse those manually with the navigation API below.


Manual navigation

When the shape is dynamic or unknown, parse to a JsonNode and walk it.

Traversing

json.parse(s)          : JsonNode      // root node
json.get(node, key)    : JsonNode      // object field by name
json.at(node, index)   : JsonNode      // array element by index
json.keys(node)        : string[]      // an object's keys
json.length(node)      : int           // array length / object field count
json.isNull(node)      : bool          // is this JSON null

Extracting scalars

json.string(node) : string
json.int(node)    : int
json.float(node)  : float
json.bool(node)   : bool
fn main() {
    JsonNode root = json.parse("{\"name\":\"Alice\",\"age\":28,\"tags\":[\"a\",\"b\"]}")

    string name = json.string(json.get(root, "name"))   // Alice
    int    age  = json.int(json.get(root, "age"))        // 28

    JsonNode tags = json.get(root, "tags")
    sys.output(json.length(tags).toString())             // 2
    sys.output(json.string(json.at(tags, 0)))            // a

    string[] keys = json.keys(root)
    for (string key in keys) {
        sys.output(key)                                  // name, age, tags
    }
}

Convenience getters

To fetch and extract a scalar in one call, use the combined getters. They are shorthand for json.<scalar>(json.get(...)) / json.<scalar>(json.at(...)).

json.getString(node, key)   : string      // = json.string(json.get(node, key))
json.getInt(node, key)      : int
json.getFloat(node, key)    : float
json.getBool(node, key)     : bool

json.atString(node, index)  : string      // = json.string(json.at(node, index))
json.atInt(node, index)     : int
json.atFloat(node, index)   : float
json.atBool(node, index)    : bool
fn main() {
    JsonNode root = json.parse("{\"name\":\"Alice\",\"age\":28}")
    sys.output(json.getString(root, "name"))   // Alice
    sys.output(json.getInt(root, "age"))        // 28
}

Quick reference

CallReturnsPurpose
json.from(value)stringSerialize any value to JSON
json.parse(s)JsonNodeParse to a navigable node
json.parse<T>(s)TDeserialize into a primitive/struct/list
json.get(node, key)JsonNodeObject field by name
json.at(node, i)JsonNodeArray element by index
json.keys(node)string[]Object keys
json.length(node)intArray length / field count
json.isNull(node)boolIs JSON null
json.string/int/float/bool(node)scalarExtract a scalar from a node
json.getString/getInt/getFloat/getBool(node, key)scalarGet + extract by key
json.atString/atInt/atFloat/atBool(node, i)scalarGet + extract by index

Picking the right tool

  • Producing JSON from your data → json.from.
  • Consuming JSON of a known shape → json.parse<T> into a struct.
  • Consuming dynamic/unknown JSON → json.parse then get/at/keys and the scalar extractors.
  • Parsing a union → navigate manually; typed parse doesn’t cover unions.