Independent implementation of RFC 8259. Covers all JSON value types including full escape-sequence handling (surrogate pairs, \uXXXX). Does not support comments, trailing commas, or single-quoted strings by design. Round-trip property: Json.parse(v.into()) == v for any valid JsonValue.

let v = Json.parse("{\"name\": \"alice\", \"age\": 30}")
match v.as_object() {
    Some(m) => {
        assert(m.get("name") == Some(Str("alice")))
        assert(m.get("age")  == Some(Num(30.0)))
    }
    None => assert(false)
}

Types

JsonValue

stable since 0.1
export type JsonValue
    | Null
    | Bool(bool)
    | Num(f64)
    | Str(str)
    | Array([]JsonValue)
    | Object(HashMap[str, JsonValue])

Recursive sum-type for arbitrary JSON documents. Structural equality is supported — v1 == v2 works for deep comparison including nested arrays and objects.

ParseJsonError

stable since 0.1
export type ParseJsonError
    | UnexpectedChar  { line int; col int; found char }
    | UnexpectedEof   { line int; col int }
    | InvalidEscape   { line int; col int; seq str }
    | InvalidUnicode  { line int; col int; hex str }
    | InvalidNumber   { line int; col int; text str }
    | DuplicateKey    { line int; col int; key str }
    | TrailingContent { line int; col int }

All variants carry 1-based line and col for diagnostic messages.


Parsing

Json.parse

stable since 0.1
fn Json.parse(s str) Fail[ParseJsonError] -> JsonValue

Parses a JSON string. Throws Fail[ParseJsonError] on any lexical or syntactic error. Use a with Fail[ParseJsonError] block to handle errors.

Examples

let arr = Json.parse("[1, 2, 3]")
assert(arr.is_array())

let obj = Json.parse("{\"x\": true}")
match obj.as_object() {
    Some(m) => assert(m.get("x") == Some(Bool(true)))
    None    => assert(false)
}

Constructors

Convenience static constructors for building JsonValue programmatically:

fn JsonValue.null()                                  -> Self  // Null
fn JsonValue.bool(b bool)                            -> Self  // Bool(b)
fn JsonValue.num(n f64)                              -> Self  // Num(n)
fn JsonValue.str(s str)                              -> Self  // Str(s)
fn JsonValue.array(items []JsonValue)                -> Self  // Array(items)
fn JsonValue.object(fields HashMap[str, JsonValue])  -> Self  // Object(fields)

Accessors

fn JsonValue @as_bool()   -> Option[bool]
fn JsonValue @as_num()    -> Option[f64]
fn JsonValue @as_str()    -> Option[str]
fn JsonValue @as_array()  -> Option[[]JsonValue]
fn JsonValue @as_object() -> Option[HashMap[str, JsonValue]]

Each returns None if the variant doesn't match. Combine with match or if let Some:

let v = Json.parse("42")
assert(v.as_num() == Some(42.0))
assert(v.as_str() == None)

Type guards

fn JsonValue @is_null()   -> bool
fn JsonValue @is_bool()   -> bool
fn JsonValue @is_num()    -> bool
fn JsonValue @is_str()    -> bool
fn JsonValue @is_array()  -> bool
fn JsonValue @is_object() -> bool

Serialization

into — compact

stable since 0.1
fn JsonValue @into() -> str

Serializes to compact JSON (single line, no extra whitespace). This is the D73 Into[str] implementation, so str.from(v) works too. Round-trip: Json.parse(v.into()) == v.

Examples

let v = Array([Num(1.0), Str("a")])
let s str = v.into()
assert(s == "[1,\"a\"]")

pretty

stable since 0.1
fn pretty(v JsonValue) -> str

Pretty-prints with 2-space indentation. Not part of Into[str] — call explicitly when human-readable output is needed (logs, debug output). Use into() for compact canonical output.

Examples

let mut m = HashMap[str, JsonValue].new()
m.insert("name", Str("alice"))
let s = pretty(Object(m))
assert(s.contains("\n"))  // multi-line