JSON (std::json)
Nesta página
std::json converte entre valores Zolo e texto JSON. Os mapas #{} viram
objetos JSON, arrays viram arrays JSON e primitivos mapeiam diretamente.
Encode (serialização)
json.encode(valor) — alias json.stringify — produz uma string JSON:
Primitivos, mapas e arrays; stringify é o mesmo que encode.
// Feature: json.encode — serialize a Zolo value to a JSON string
// When to use: send data over HTTP, save to file, structured logs.
use std::json
// Primitive types map 1:1 to JSON.
print(json.encode(42)) // expected: 42
print(json.encode(3.14)) // expected: 3.14
print(json.encode(true)) // expected: true
print(json.encode(nil)) // expected: null
print(json.encode("hello")) // expected: "hello"
// Map literal becomes a JSON object.
let user = #{name: "Alice", age: 30, active: true}
print(json.encode(user)) // expected: {"name":"Alice", ...}
// Array becomes a JSON array.
const nums = [1, 2, 3, 4, 5]
print(json.encode(nums)) // expected: [1,2,3,4,5]
// `stringify` is an alias for `encode` (JS-style).
print(json.stringify(#{k: "v"})) // expected: {"k":"v"}
Decode (desserialização)
json.decode(string) — alias json.parse — converte JSON em valor Zolo.
Use strings brutas r#"..."# para evitar a cerimônia de escapar aspas:
Objeto e array decodificados; parse é o mesmo que decode.
// Feature: json.decode — convert a JSON string to a Zolo value
// When to use: parse API payloads, read JSON config files.
// For inline JSON literals use the tagged literal `json"..."` — it is more
// readable than escaping everything with `\"` and `\{`.
use std::json
// Decode primitives. `json.decode` takes a STRING; the tagged
// literal `json"..."` is itself a parsed Zolo value (table), so use
// raw strings (`r#"..."#`) as input here to avoid escape ceremony.
print(json.decode("42")) // expected: 42
print(json.decode(r#""hi""#)) // expected: hi
print(json.decode("true")) // expected: true
print(json.decode("null")) // expected: nil
// Decode object -> map.
let obj = json.decode(r#"{"name":"Bob","age":25}"#)
print(obj["name"]) // expected: Bob
print(obj["age"]) // expected: 25
// Decode array. Arrays are 0-indexed via `[]` in Zolo.
let arr = json.decode("[10, 20, 30]")
print(arr[0]) // expected: 10
print(arr[2]) // expected: 30
// `parse` is an alias for `decode` (JS-style).
let parsed = json.parse(r#"{"ok":true}"#)
print(parsed["ok"]) // expected: true
// Tip: when you have inline JSON, the tagged literal `json"..."`
// returns a Zolo value directly — no decode call needed.
Estruturas Aninhadas
Objetos dentro de arrays e vice-versa — round-trip sem perda:
Encode → decode preserva hierarquia de mapas e arrays.
// Feature: json — nested values (objects inside arrays and vice versa)
// When to use: complex JSON documents from REST APIs.
use std::json
// Object that contains an array.
let post = #{
id: 1,
title: "Hello",
tags: ["zolo", "json", "stdlib"],
author: #{name: "Alice", email: "alice@example.com"},
}
let encoded = json.encode(post)
print(encoded)
// Round-trip: encode -> decode -> same structure.
let decoded = json.decode(encoded)
print(decoded["title"]) // expected: Hello
print(decoded["tags"][1]) // expected: json
print(decoded["author"]["name"]) // expected: Alice
// Array of objects.
let users = [
#{name: "Alice", age: 30},
#{name: "Bob", age: 25},
]
let raw = json.encode(users)
let back = json.decode(raw)
print(back[0]["name"]) // expected: Alice
print(back[1]["age"]) // expected: 25
Tratamento de Erros
json.decode devolve (nil, msg_erro) quando a entrada é inválida. O padrão
idiomático é envolver numa função que retorna Result:
Destructuring duplo let v, e = json.decode(s) para checar falhas.
// Feature: json.decode — error handling
// When to use: parsing payloads with no guarantee they are valid JSON.
// json.decode returns (nil, err_msg) on failure — standard return protocol.
use std::Result
use std::json
// json.decode takes a STRING — use raw strings to avoid escapes.
let valid = json.decode(r#"{"ok":true}"#)
print(valid["ok"]) // expected: true
// Invalid input -> value is nil, err carries a message.
let bad, err = json.decode("garbage payload")
print(bad) // expected: nil
print(err != nil) // expected: true
// Idiomatic pattern: wrap in a Result.
fn safe_parse(s: str) {
let v, e = json.decode(s)
if e != nil { return Result.Err(e) }
return Result.Ok(v)
}
let r1 = safe_parse("42")
let r2 = safe_parse("not valid json text")
print(r1.is_ok()) // expected: true
print(r2.is_err()) // expected: true
Literal json"..."
O literal marcado json#"..."# é resolvido em tempo de compilação — sem
chamada a decode em tempo de execução, sem escapes:
Defaults de configuração como literal JSON compilado.
// Feature: `json"..."` — JSON literal as a tagged template
// Syntax: the template body is parsed as JSON at compile time and
// emitted as a Map. No runtime decode call, no escaping ceremony.
// When to use: defaults / fixtures / inline configs that are easier
// to read as JSON than as `#{...}` map literals.
use std::Map
let defaults = json#"{ "port": 8080, "host": "localhost", "debug": true }"#
print(defaults["port"])
// expected: 8080
print(defaults["host"])
// expected: localhost
print(defaults["debug"])
// expected: true
// For simple inline maps, the `#{...}` map literal is more idiomatic
// than the `json"..."` tagged template — no JSON parsing, just keys.
let cfg = #{
name: "alice",
age: 30,
active: true,
}
print(cfg["name"]) // expected: alice
print(cfg["age"]) // expected: 30
print(cfg["active"]) // expected: true
Path Walk
valor.path("a.b.0.c") navega por um mapa aninhado com segmentos pontilhados;
segmentos numéricos indexam arrays:
path e has_path para acessar e verificar chaves profundas.
// Feature: `data.path("a.b.0.c")` — walk a nested map by dotted segments
// Syntax: numeric segments are 0-based array indices; everything else
// is a map key. `data.has_path("a.b")` is the boolean wrapper.
// When to use: pluck a single value out of a deeply-nested config or
// JSON response without writing a chain of `["a"]["b"]` indexes.
let data = #{
"users": [
#{ "name": "alice", "role": "admin" },
#{ "name": "bob", "role": "dev" },
],
"config": #{
"port": 8080,
"debug": true,
},
}
print(data.path("config.port")) // expected: 8080
print(data.path("config.debug")) // expected: true
print(data.path("users.0.name")) // expected: alice
print(data.path("users.1.role")) // expected: dev
// `has_path` returns whether the value exists.
print(data.has_path("config.port")) // expected: true
print(data.has_path("config.missing")) // expected: false
Valores Ausentes via Path
path retorna nil para segmentos inexistentes; has_path distingue "ausente"
de "presente mas falso":
has_path("config.debug") é true mesmo quando o valor é false.
// Feature: `path` and `has_path` semantics for missing / falsy values
// Rules:
// - missing segment → `nil`
// - walking through a non-table → `nil`
// - `has_path` returns `false` for missing, `true` for present-but-falsy
// When to use: distinguish "not provided" from "set to false" in
// settings / feature flags / JSON inputs.
let data = #{
"config": #{
"port": 8080,
"debug": false,
},
}
// Missing segment → nil.
print(data.path("config.missing"))
// expected: nil
print(data.path("nope.also.missing"))
// expected: nil
// `has_path` is the boolean wrapper.
print(data.has_path("config.missing"))
// expected: false
// A `false` *value* is still present.
print(data.has_path("config.debug"))
// expected: true
// Walking past a leaf (port is an int) gives nil.
print(data.path("config.port.deeper"))
// expected: nil
Interop com Schemas
Toda declaração schema ganha from_json(str) -> Result e to_json() -> str
automaticamente:
Point.from_json valida + decodifica; p.to_json() serializa a instância.
// Feature: `Schema.from_json` / `instance.to_json()` round-trip
// Syntax: every `schema` declaration auto-generates `from_json(str)`
// (decodes + parses, returns Result) and `instance.to_json()` (encodes
// via to_map + json.encode).
// When to use: end-to-end validation of JSON requests/responses.
// Combines the validation in `features/23-schemas/` with
// `json.encode` / `json.decode`.
use std::Result
use std::json
schema Point {
x: int,
y: int,
}
// from_json: decode JSON, then run through Schema.parse so all
// `where` constraints fire. Returns Result.
let raw = "\{ \"x\": 3, \"y\": 4 \}"
match Point.from_json(raw) {
Result::Ok(p) => {
print(p.x) // expected: 3
print(p.y) // expected: 4
},
Result::Err(e) => print("error: {e}"),
}
// to_json: instance → JSON string. Key order is not stable, but the
// substrings are. (We use `Point.new(...)` here — the factory built
// for every schema.)
let p = Point.new(3, 4)
let s = p.to_json()
print(s.contains("\"x\":3")) // expected: true
print(s.contains("\"y\":4")) // expected: true
Veja também