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?