RustArena

Step 1 of 6

From structs to JSON

Programs at work constantly swap data with other programs: a web API answers in JSON, a config file is TOML, a message on a queue is JSON again. Turning a value in memory into text (or bytes) like that is called serialization; reading it back into a value is deserialization. In Rust nearly every team uses one library for both: serde (from SERialize and DEserialize).

serde comes in two halves:

In a real project you add both to Cargo.toml (the derive feature switches on the derive macros):

[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"

On this site both are already available.

Your first JSON

use serde::Serialize;

#[derive(Serialize)]
struct Player {
    name: String,
    level: u32,
    online: bool,
}

fn main() {
    let p = Player { name: String::from("Ferris"), level: 7, online: true };
    let json = serde_json::to_string(&p).unwrap();
    println!("{json}");
}
{"name":"Ferris","level":7,"online":true}

Piece by piece:

How Rust types map to JSON

Rust JSON
String, &str, char string "..."
integers, floats number
bool true / false
Vec<T>, arrays, slices, tuples array [...]
Option<T> the value, or null for None
structs, BTreeMap<String, T>, HashMap<String, T> object {...}

Structs can contain other structs, as long as every type inside also implements Serialize. For humans, to_string_pretty adds newlines and two-space indentation:

use serde::Serialize;

#[derive(Serialize)]
struct Item {
    sku: String,
    qty: u32,
}

#[derive(Serialize)]
struct Order {
    id: u64,
    items: Vec<Item>,
    note: Option<String>,
}

fn main() {
    let order = Order {
        id: 1001,
        items: vec![Item { sku: String::from("CRAB-01"), qty: 2 }],
        note: None,
    };
    println!("{}", serde_json::to_string_pretty(&order).expect("orders always serialize"));
}
{
  "id": 1001,
  "items": [
    {
      "sku": "CRAB-01",
      "qty": 2
    }
  ],
  "note": null
}

Common mistakes

Forgetting the derive on an inner type. Order derives Serialize, but Item doesn't:

use serde::Serialize;

struct Item {
    sku: String,
}

#[derive(Serialize)]
struct Order {
    items: Vec<Item>,
}

fn main() {
    let o = Order { items: vec![Item { sku: String::from("A") }] };
    println!("{}", serde_json::to_string(&o).unwrap());
}
error[E0277]: the trait bound `Item: serde::Serialize` is not satisfied

The note under the error even suggests the fix: add #[derive(Serialize)] to Item. Two more:

Your task

Fill in the blanks so the program derives the trait and prints the ticket as compact JSON: {"id":42,"title":"Login button broken","open":true}.

Challenge 2

The starter code doesn't compile: one type is missing its derive. Fix it, then print the inventory with serde_json::to_string_pretty so the output is indented over several lines.

Challenge 3

Logs are often written as JSON Lines: one compact JSON object per line, so tools can process them line by line. Derive what Event needs and write fn to_json_lines(events: &[Event]) -> String that serializes each event with serde_json::to_string and joins them with \n (no newline after the last one; an empty slice gives an empty string).

Next: From JSON back into structs