Skip to content

JSON Decoding with ard/decode

The ard/decode module provides a composable system for decoding JSON and other data into Ard values with detailed error reporting.

The decode module provides:

  • Primitive decoders for strings, integers, floats, and booleans
  • Composable decoders that can be combined for complex types
  • Detailed error messages with path information for debugging
  • JSON parsing with the from_json function
use ard/decode
use ard/io
fn main() {
let json_str = "{\"name\": \"Alice\", \"age\": 30}"
let data = decode::from_json(json_str).expect("Invalid JSON")
let name = decode::run(data, decode::field("name", decode::string)).expect("Failed to decode")
let age = decode::run(data, decode::field("age", decode::int)).expect("Failed to decode")
io::print("Name: {name}, Age: {age.to_str()}")
}

Decode a string value from Dynamic data.

use ard/decode
let data = Dynamic::from("hello")
let str = decode::run(data, decode::string).expect("Failed to decode")

Decode an integer value from Dynamic data.

use ard/decode
let data = Dynamic::from(42)
let num = decode::run(data, decode::int).expect("Failed to decode")

Decode a float value from Dynamic data.

use ard/decode
let data = Dynamic::from(3.14)
let num = decode::run(data, decode::float).expect("Failed to decode")

Decode a boolean value from Dynamic data.

use ard/decode
let data = Dynamic::from(true)
let flag = decode::run(data, decode::bool).expect("Failed to decode")

fn nullable(decoder: Decoder<$T>) fn(Dynamic) $T?![Error]

Section titled “fn nullable(decoder: Decoder<$T>) fn(Dynamic) $T?![Error]”

Create a decoder that handles nullable values. Returns none for null data, or the result of applying the inner decoder.

use ard/decode
let string_decoder = decode::nullable(decode::string)
let data = Dynamic::from_void()
let maybe_str = decode::run(data, string_decoder).expect("") // none

fn list(decoder: Decoder<$T>) fn(Dynamic) [$T]![Error]

Section titled “fn list(decoder: Decoder<$T>) fn(Dynamic) [$T]![Error]”

Create a decoder for a list of items. Applies the inner decoder to each element and reports detailed errors with array indices.

use ard/decode
let data = decode::from_json("[1, 2, 3]").expect("Invalid JSON")
let numbers = decode::run(data, decode::list(decode::int)).expect("Failed to decode")

fn field(name: Str, with: Decoder<$T>) Decoder<$T>

Section titled “fn field(name: Str, with: Decoder<$T>) Decoder<$T>”

Create a decoder for a specific field in an object. Combines the decoder for the field value with path tracking.

use ard/decode
let data = decode::from_json("{\"name\": \"Alice\"}").expect("Invalid JSON")
let name = decode::run(data, decode::field("name", decode::string)).expect("Failed to decode")

fn path(subpath: [PathSegment], with: Decoder<$T>) Decoder<$T>

Section titled “fn path(subpath: [PathSegment], with: Decoder<$T>) Decoder<$T>”

Create a decoder for a nested path supporting both field names and array indices. PathSegment is a union type (Str | Int) allowing you to navigate through objects and arrays.

use ard/decode
// Navigate through nested objects
let data = decode::from_json("{\"user\": {\"profile\": {\"age\": 30}}}").expect("Invalid JSON")
let age = decode::run(data, decode::path(["user", "profile", "age"], decode::int)).expect("Failed to decode")
// Navigate through arrays and objects
let data2 = decode::from_json("{\"users\": [{\"name\": \"Alice\"}, {\"name\": \"Bob\"}]}").expect("Invalid JSON")
let name = decode::run(data2, decode::path(["users", 0, "name"], decode::string)).expect("Failed to decode")

fn map(key: Decoder<$Key>, value: Decoder<$Value>) Decoder<[$Key:$Value]>

Section titled “fn map(key: Decoder<$Key>, value: Decoder<$Value>) Decoder<[$Key:$Value]>”

Create a decoder for maps/objects with custom key and value decoders.

use ard/decode
let data = decode::from_json("{\"alice\": 30, \"bob\": 25}").expect("Invalid JSON")
let ages = decode::run(data, decode::map(decode::string, decode::int)).expect("Failed to decode")

fn one_of(first: Decoder<$T>, others: [Decoder<$T>]) Decoder<$T>

Section titled “fn one_of(first: Decoder<$T>, others: [Decoder<$T>]) Decoder<$T>”

Create a decoder that tries multiple decoders in sequence, returning the first successful result.

use ard/decode
let data = Dynamic::from(42)
let flexible_decoder = decode::one_of(decode::string, [decode::int, decode::float])
let result = decode::run(data, flexible_decoder).expect("Failed to decode")

Decoder that simply maintains the Dynamic value as-is. This can be used as a no-op or simple identity decoder.

use ard/decode
let data = Dynamic::from("anything")
let result = decode::run(data, decode::dynamic).expect("Failed to decode")

Parse a JSON string into a Dynamic value. Returns an error if the JSON is invalid.

use ard/decode
let data = decode::from_json("{\"name\": \"Alice\"}").expect("Invalid JSON")

Check if a Dynamic value is null/void.

use ard/decode
let data = Dynamic::from_void()
if decode::is_void(data) {
// handle null
}

fn run(data: Dynamic, decoder: Decoder<$T>) $T![Error]

Section titled “fn run(data: Dynamic, decoder: Decoder<$T>) $T![Error]”

Apply a decoder to Dynamic data. This is a shorthand for applying a decoder function directly.

use ard/decode
let data = Dynamic::from(42)
let num = decode::run(data, decode::int).expect("Failed to decode")

Represents a decoding error with detailed context.

  • expected: Str - What type was expected
  • found: Str - What was actually found
  • path: [Str] - The path to the error location (e.g., [“user”, “age”])

The Error struct implements ToString for readable error messages:

use ard/decode
let data = Dynamic::from("not_a_number")
match decode::run(data, decode::int) {
err(errors) => {
for err in errors {
io::print(err.to_str()) // "Decode error: expected Int, found "not_a_number""
}
},
ok(_) => {}
}
use ard/decode
use ard/io
fn main() {
let json = "{\"name\": \"Alice\", \"age\": 30}"
let data = decode::from_json(json).expect("Invalid JSON")
let name = decode::run(data, decode::field("name", decode::string)).expect("Failed")
let age = decode::run(data, decode::field("age", decode::int)).expect("Failed")
io::print("Name: {name}")
io::print("Age: {age.to_str()}")
}
use ard/decode
use ard/io
fn main() {
let json = "[1, 2, 3, 4, 5]"
let data = decode::from_json(json).expect("Invalid JSON")
let numbers = decode::run(data, decode::list(decode::int)).expect("Failed to decode")
for num in numbers {
io::print(num.to_str())
}
}
use ard/decode
use ard/io
fn main() {
let json = "{\"user\": {\"name\": \"Alice\", \"age\": \"thirty\"}}"
let data = decode::from_json(json).expect("Invalid JSON")
let decoder = decode::field("user",
decode::field("age", decode::int)
)
match decode::run(data, decoder) {
ok(age) => io::print(age.to_str()),
err(errors) => {
for error in errors {
io::print(error.to_str()) // "Decode error: expected Int, found "thirty" at user.age"
}
}
}
}
use ard/decode
use ard/io
use ard/maybe
struct Person {
name: Str,
email: Str?
}
fn main() {
let json1 = "{\"name\": \"Alice\", \"email\": \"alice@example.com\"}"
let json2 = "{\"name\": \"Bob\", \"email\": null}"
let decoder = decode::field("email", decode::nullable(decode::string))
let data1 = decode::from_json(json1).expect("")
let email1 = decode::run(data1, decoder).expect("") // some("alice@example.com")
let data2 = decode::from_json(json2).expect("")
let email2 = decode::run(data2, decoder).expect("") // none
}
use ard/decode
use ard/io
fn main() {
// Decoder that accepts either a string or an integer
let flexible = decode::one_of(decode::string, [decode::int])
let str_data = Dynamic::from("hello")
let int_data = Dynamic::from(42)
let str_result = decode::run(str_data, flexible).expect("")
let int_result = decode::run(int_data, flexible).expect("")
}