RustArena

Step 2 of 6

From JSON back into structs

The other direction is deserialization: you derive Deserialize and ask serde_json::from_str for a value of your type.

use serde::Deserialize;

#[derive(Deserialize)]
struct Reading {
    sensor: String,
    celsius: f64,
}

fn main() {
    let text = r#"{"sensor": "greenhouse", "celsius": 21.5, "battery": 87}"#;
    let r: Reading = serde_json::from_str(text).unwrap();
    println!("{} reads {:.1} C", r.sensor, r.celsius);
}
greenhouse reads 21.5 C

Piece by piece:

What can go wrong

serde checks the shape of the data against your type: every field present, every value the right type and in range. Anything else is an Err whose message says what and where:

use serde::Deserialize;

#[derive(Deserialize)]
struct Reading {
    sensor: String,
    celsius: f64,
}

fn main() {
    let inputs = [
        r#"{"sensor": "attic", "celsius": 30}"#,
        r#"{"sensor": "attic"}"#,
        r#"{"sensor": "attic", "celsius": "hot"}"#,
        r#"{"sensor": "attic", "celsius": 30"#,
    ];
    for text in inputs {
        match serde_json::from_str::<Reading>(text) {
            Ok(r) => println!("ok: {} = {}", r.sensor, r.celsius),
            Err(e) => println!("error: {e}"),
        }
    }
}
ok: attic = 30
error: missing field `celsius` at line 1 column 19
error: invalid type: string "hot", expected f64 at line 1 column 36
error: EOF while parsing an object at line 1 column 33

A JSON integer like 30 is fine for an f64 field. The reverse isn't: 2.5 for a u32 is an error, and so is -3 for a u32 or 300 for a u8. Your Rust types are the validation: pick them carefully.

Common mistakes

Your task

A shop sends carts as JSON like {"items": [{"name": "mug", "price": 2.5, "qty": 2}]}. Derive what's needed and write fn cart_total(json: &str) -> Result<f64, serde_json::Error> that parses the cart and returns the sum of price * qty over all items. Invalid JSON must give an Err (use ?).

Challenge 2

Read a JSON array of players from standard input, like [{"name": "Ferris", "score": 950}, ...], and print the best one as top: Ferris (950). If the array is empty, print no players. You can assume the input is valid JSON.

Challenge 3

A round trip: write fn rename_sensor(json: &str, new_name: &str) -> Result<String, serde_json::Error> that parses a sensor reading, changes its sensor field to new_name, and returns it serialized again as compact JSON. The struct needs both derives. Errors go back to the caller with ?.

Previous: From structs to JSON Next: Shaping the JSON: rename, default, skip