🎓 A1Learner
🔍

Rust JSON Values in Depth

Value is the serde_json type that holds any JSON shape. Value accepts every such shape without a fixed struct.

Six kinds, one type

A Value can hold any JSON piece. Small test methods name the kind it holds.

let v = serde_json::json!({"ok": true, "count": 3, "note": null});
println!("{} {} {}", v["ok"].is_boolean(), v["count"].is_number(), v["note"].is_null());

as_ methods answer with Option

An as_ method fits only its own kind. The wrong kind gives None. Nothing panics.

let v = serde_json::json!({"name": "Ada", "age": 36});
println!("{:?}", v["name"].as_str());
println!("{:?}", v["age"].as_str());

A missing key gives Null

Indexing never fails on a Value. A key that is not there reads back as Null. get returns an Option for when the difference matters.

let v = serde_json::json!({"name": "Ada"});
println!("{}", v["missing"]);
println!("{:?}", v.get("missing"));

Walk an array

as_array hands you the list inside. Each item is a Value again.

let v = serde_json::json!([10, 20, 30]);
let items = v.as_array().unwrap();
let total: i64 = items.iter().filter_map(|n| n.as_i64()).sum();
println!("total = {}", total);

Walk an object's keys

as_object hands you the map inside. The pairs come out in sorted key order.

let v = serde_json::json!({"b": 2, "a": 1});
for (k, val) in v.as_object().unwrap() {
    println!("{k}={val}");
}

Turn a Value into a struct

A Value can change after parsing. Person is the struct from the JSON lesson. Index assignment writes a new key. from_value then fills the typed struct.

let mut v = serde_json::json!({"name": "Ada"});
v["age"] = serde_json::json!(36);
let p: Person = serde_json::from_value(v).unwrap();
println!("{} is {}", p.name, p.age);

Example

// Cargo.toml: serde = { version = "1.0.229", features = ["derive"] }, serde_json = "1.0.154"
use serde_json::Value;

fn main() {
    let text = "[{\"name\":\"Ada\",\"score\":10},{\"name\":\"Grace\"},{\"name\":\"Alan\",\"score\":7}]";
    let rows: Value = serde_json::from_str(text).unwrap();
    let mut total = 0;
    let mut missing = 0;
    for row in rows.as_array().unwrap() {
        match row["score"].as_i64() {
            Some(s) => total += s,
            None => missing += 1,
        }
    }
    println!("total = {total}");
    println!("missing = {missing}");
}

Key points

  • Value has exactly six kinds.
  • They are Null, Bool, Number, String, Array and Object.
  • to_value turns a struct into a Value.
  • from_value turns a Value back into a struct.
  • A Number remembers if it was whole.
  • as_i64 reads whole numbers.
  • as_f64 reads any number.
  • Prefer a typed struct when the shape is known.
  • The compiler then checks every field for you.
  • A bad guess gives None or Null, never a compile error.
  • A log file may mix record kinds, and a config may hold anything at all.
  • One Value can stand for any piece of JSON.

In this example, three records arrive as one JSON array. The shapes differ. One record has no score at all. The program reads the text as a Value, so no record can break the parse. It walks the array and asks each record for its score. Two records answer with a number. One record stays silent, so the missing counter grows. The totals print at the end.

Note

A Value is checked only while your program runs.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

v["age"] holds the text "36". What does v["age"].as_i64() give back?