🎓 A1Learner
🔍
0 of 149 lessons completed

Rust Serde Attributes

Serde attributes are small notes on a struct or a field that bend one serde rule for one spot. An attribute can make an outside name match the Rust name.

rename bends one name

Put rename above a single field. Serde uses the new name in the JSON text. Your Rust code keeps the old name.

#[serde(rename = "homeTown")]
home_town: String,

rename_all bends every name

Put rename_all on the struct itself. Every field name switches to the style you name.

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct Player {
    player_name: String,
    high_score: u32,
}

let p = Player { player_name: String::from("Ada"), high_score: 900 };
println!("{}", serde_json::to_string(&p).unwrap());

default fills a missing field

A field with default may stay out of the text. Serde fills it with the type's Default value.

#[derive(Deserialize, Debug)]
struct Settings {
    volume: u32,
    #[serde(default)]
    muted: bool,
}

let s: Settings = serde_json::from_str("{\"volume\":7}").unwrap();
println!("{:?}", s);

default can call your function

Name a function when the fallback is not the plain Default. The function takes no inputs. It returns the field type.

fn standard_port() -> u16 { 8080 }

#[derive(Deserialize, Debug)]
struct Server {
    host: String,
    #[serde(default = "standard_port")]
    port: u16,
}

let s: Server = serde_json::from_str("{\"host\":\"localhost\"}").unwrap();
println!("{}:{}", s.host, s.port);

skip leaves a field out

skip removes a field from the text for good. skip_serializing_if asks a small function first. The field stays out only when the function says true.

#[derive(Serialize)]
struct Draft {
    title: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    note: Option<String>,
    #[serde(skip)]
    edits: u32,
}

let d = Draft { title: String::from("Ideas"), note: None, edits: 3 };
println!("{}", serde_json::to_string(&d).unwrap());

flatten merges a nested struct

flatten pours a nested struct's fields into the parent object. The JSON loses one level of nesting. Your Rust types keep it.

#[derive(Deserialize, Debug)]
struct Size { width: u32, height: u32 }

#[derive(Deserialize, Debug)]
struct Page {
    number: u32,
    #[serde(flatten)]
    size: Size,
}

let p: Page = serde_json::from_str("{\"number\":1,\"width\":800,\"height\":600}").unwrap();
println!("{} {}x{}", p.number, p.size.width, p.size.height);

Example

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

#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct Profile {
    user_name: String,
    level: u32,
    #[serde(default)]
    sound_on: bool,
    #[serde(skip_serializing_if = "Option::is_none")]
    motto: Option<String>,
}

fn main() {
    let text = "{\"userName\":\"Ada\",\"level\":5}";
    let mut p: Profile = serde_json::from_str(text).unwrap();
    println!("sound on = {}", p.sound_on);
    p.sound_on = true;
    p.motto = Some(String::from("Be curious"));
    println!("{}", serde_json::to_string(&p).unwrap());
}

Key points

  • Container attributes sit above the struct or enum.
  • Field attributes sit above one field.
  • Use rename for one stubborn name.
  • Use rename_all when one style rules every field, like camelCase.
  • A field skipped on reading comes back with its Default value.
  • Add a default attribute too when that value is wrong.
  • flatten pours a nested struct into the parent object.
  • It can slow reading a little, because the reader must buffer values first.
  • The data itself stays the same on both sides.
  • Real APIs send camelCase names like homeTown, while Rust code prefers snake_case names like home_town.
  • A serde attribute changes one serde rule at one place.

In this example, a game profile arrives as JSON with camelCase names. rename_all lets the Rust fields stay in snake_case. The text has no soundOn key. The default attribute fills sound_on with false. The motto starts as None. Main then turns the sound on and sets a motto. The last line prints the struct as JSON again. Every name is camelCase. The motto appears now, because it holds a value.

Note

Attributes bend names and rules only.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

An API sends the key homeTown. Your Rust field is home_town. What makes the two match for that field alone?