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?