Skip to content
Select theme

How to rename JSON fields with serde

Short answer: #[serde(rename = "fullName")] renames one field, and #[serde(rename_all = "camelCase")] on the struct renames every field at once. Both work on the way in and on the way out: the same attribute tells serde which key to read and which key to write.

The starting point is the json-demo project from the previous article — cargo new json-demo, with the same two crates in Cargo.toml:

Cargo.toml
[dependencies]
serde = { version = "1.0.229", features = ["derive"] }
serde_json = "1.0.154"

The situation this solves: the API on the other end sends camelCase keys (fullName, accountId, isActive), while Rust field names are snake_case by convention. The API is not going to change. serde bridges the two without renaming a single Rust field.

src/main.rs
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize)]
struct User {
id: u64,
#[serde(rename = "fullName")]
full_name: String,
}
fn main() {
let json = r#"{"id":7,"fullName":"Grace Hopper"}"#;
let user: User = serde_json::from_str(json).unwrap();
println!("{user:?}");
println!("{}", serde_json::to_string(&user).unwrap());
}
User { id: 7, full_name: "Grace Hopper" }
{"id":7,"fullName":"Grace Hopper"}

rename sits on the field and takes the wire name as a string. It covers both directions: from_str finds fullName and puts it in full_name, and to_string writes full_name back out as fullName. The Rust name never appears in the JSON.

When the whole payload follows one convention, an attribute per field is noise. rename_all on the struct applies one rule to every field in it:

src/main.rs
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
struct Account {
account_id: u64,
display_name: String,
is_active: bool,
}
fn main() {
let json = r#"{"accountId":42,"displayName":"Ada","isActive":true}"#;
let account: Account = serde_json::from_str(json).unwrap();
println!("{account:?}");
println!("{}", serde_json::to_string(&account).unwrap());
}
Account { account_id: 42, display_name: "Ada", is_active: true }
{"accountId":42,"displayName":"Ada","isActive":true}

Eight rules are accepted, and the compiler lists them when the value is wrong:

use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "kebabCase")]
struct Account {
account_id: u64,
}
fn main() {
let account = Account { account_id: 1 };
println!("{account:?}");
}
error: unknown rename rule `rename_all = "kebabCase"`, expected one of "lowercase", "UPPERCASE", "PascalCase", "camelCase", "snake_case", "SCREAMING_SNAKE_CASE", "kebab-case", "SCREAMING-KEBAB-CASE"
--> src/main.rs:4:22
|
4 | #[serde(rename_all = "kebabCase")]
| ^^^^^^^^^^^

Note the casing: the rule is kebab-case with a hyphen, not kebabCase. rename_all also applies to enum variants, so a #[serde(rename_all = "camelCase")] enum reads and writes "inProgress" for a variant named InProgress.

A payload that is mostly one convention with one odd key out does not need rename_all abandoned: a field-level rename overrides the container rule for that field, so account_id can be accountId while legacy_id stays legacy_id.

rename_all can also be given a direction. When the API sends camelCase but the JSON this program writes should stay snake_case, rename_all(deserialize = "camelCase") renames on the way in only:

src/main.rs
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all(deserialize = "camelCase"))]
struct Account {
account_id: u64,
}
fn main() {
let account: Account = serde_json::from_str(r#"{"accountId":3}"#).unwrap();
println!("{}", serde_json::to_string(&account).unwrap());
}
{"account_id":3}

One more attribute belongs on this page: rename controls what serde writes, but it does not have to be the only key serde accepts. #[serde(alias = "name")] adds a second accepted name on the way in, so the two together read both "name" and "fullName" while always writing "fullName".

The next article in this chapter covers the other half of the problem: a key that is not there at all — absent, null, or spelled something else.