Loading pageā¦
Rust walkthroughs
Loading pageā¦
serde with #[serde(rename_all = "...")], how does it interact with #[serde(rename = "...")] on individual fields?#[serde(rename_all = "...")] applies a case conversion rule to all fields or variants in a type, while #[serde(rename = "...")] overrides the name for a specific field or variant. When both are present, the individual rename attribute takes precedenceāthe rename_all transformation is applied to all fields except those with explicit rename attributes. This allows you to establish a default naming convention with rename_all while making targeted exceptions with individual rename attributes. The interaction is compositional: rename_all sets the baseline transformation, and rename provides field-specific overrides.
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct UserProfile {
user_id: u32,
first_name: String,
last_name: String,
is_active: bool,
}
fn rename_all_example() {
let profile = UserProfile {
user_id: 42,
first_name: "Alice".to_string(),
last_name: "Smith".to_string(),
is_active: true,
};
let json = serde_json::to_string(&profile).unwrap();
println!("{}", json);
// {"userId":42,"firstName":"Alice","lastName":"Smith","isActive":true}
// All snake_case fields are converted to camelCase in JSON
}rename_all = "camelCase" converts all snake_case field names to camelCase in the serialized output.
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct Config {
max_retries: u32,
timeout_seconds: u32,
#[serde(rename = "apiEndpoint")]
api_url: String, // This field has a custom name
debug_mode: bool,
}
fn rename_override_example() {
let config = Config {
max_retries: 3,
timeout_seconds: 30,
api_url: "https://api.example.com".to_string(),
debug_mode: false,
};
let json = serde_json::to_string(&config).unwrap();
println!("{}", json);
// {"maxRetries":3,"timeoutSeconds":30,"apiEndpoint":"https://api.example.com","debugMode":false}
// max_retries -> maxRetries (from rename_all)
// timeout_seconds -> timeoutSeconds (from rename_all)
// api_url -> apiEndpoint (from individual rename, NOT apiUrl)
// debug_mode -> debugMode (from rename_all)
}The individual rename takes precedence over the rename_all rule for that specific field.
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
struct Data {
// Field name is already snake_case, so no change needed
user_name: String,
// rename_all would make this "is_active"
// but rename overrides it to "active"
#[serde(rename = "active")]
is_active: bool,
// rename_all would make this "user_id"
// rename overrides to "id"
#[serde(rename = "id")]
userId: String, // Even non-snake_case fields get transformed
}
fn precedence_example() {
let data = Data {
user_name: "bob".to_string(),
is_active: true,
userId: "123".to_string(),
};
let json = serde_json::to_string(&data).unwrap();
println!("{}", json);
// {"user_name":"bob","active":true,"id":"123"}
// user_name: unchanged (already snake_case)
// is_active: renamed to "active" (individual rename wins)
// userId: renamed to "id" (individual rename wins)
}Individual rename attributes always win over rename_all, regardless of what transformation would have been applied.
use serde::{Serialize, Deserialize};
// snake_case: user_id, first_name, is_active
#[derive(Serialize)]
#[serde(rename_all = "snake_case")]
struct SnakeCase {
user_id: u32,
first_name: String,
}
// camelCase: userId, firstName, isActive
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct CamelCase {
user_id: u32,
first_name: String,
}
// PascalCase: UserId, FirstName, IsActive (also called UpperCamelCase)
#[derive(Serialize)]
#[serde(rename_all = "PascalCase")]
struct PascalCase {
user_id: u32,
first_name: String,
}
// SCREAMING_SNAKE_CASE: USER_ID, FIRST_NAME, IS_ACTIVE
#[derive(Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
struct ScreamingSnakeCase {
user_id: u32,
first_name: String,
}
// kebab-case: user-id, first-name, is-active
#[derive(Serialize)]
#[serde(rename_all = "kebab-case")]
struct KebabCase {
user_id: u32,
first_name: String,
}
fn case_examples() {
let data = SnakeCase { user_id: 1, first_name: "A".to_string() };
println!("snake_case: {}", serde_json::to_string(&data).unwrap());
// {"user_id":1,"first_name":"A"}
let data = CamelCase { user_id: 1, first_name: "A".to_string() };
println!("camelCase: {}", serde_json::to_string(&data).unwrap());
// {"userId":1,"firstName":"A"}
let data = PascalCase { user_id: 1, first_name: "A".to_string() };
println!("PascalCase: {}", serde_json::to_string(&data).unwrap());
// {"UserId":1,"FirstName":"A"}
let data = ScreamingSnakeCase { user_id: 1, first_name: "A".to_string() };
println!("SCREAMING_SNAKE_CASE: {}", serde_json::to_string(&data).unwrap());
// {"USER_ID":1,"FIRST_NAME":"A"}
let data = KebabCase { user_id: 1, first_name: "A".to_string() };
println!("kebab-case: {}", serde_json::to_string(&data).unwrap());
// {"user-id":1,"first-name":"A"}
}Serde supports multiple case conversions for different naming conventions.
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct ApiResponse {
status_code: u32,
message: String,
// Override for clarity or API compatibility
#[serde(rename = "code")]
status_code_internal: u32,
// Override to match external API naming
#[serde(rename = "errorMessage")]
error_details: Option<String>,
// This uses rename_all (camelCase)
response_time_ms: u64,
}
fn multiple_overrides() {
let response = ApiResponse {
status_code: 200,
message: "OK".to_string(),
status_code_internal: 200,
error_details: None,
response_time_ms: 42,
};
let json = serde_json::to_string(&response).unwrap();
println!("{}", json);
// {"statusCode":200,"message":"OK","code":200,"errorMessage":null,"responseTimeMs":42}
// statusCode, message, responseTimeMs: from rename_all = "camelCase"
// code: from individual rename (overriding status_code_internal)
// errorMessage: from individual rename (overriding error_details)
}Multiple fields can have individual rename overrides while others follow rename_all.
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
enum Status {
ActiveNow, // becomes "active_now"
PendingReview, // becomes "pending_review"
#[serde(rename = "inactive")]
NotActive, // becomes "inactive" (not "not_active")
Archived, // becomes "archived"
}
fn enum_example() {
let status = Status::ActiveNow;
println!("{}", serde_json::to_string(&status).unwrap());
// "active_now"
let status = Status::NotActive;
println!("{}", serde_json::to_string(&status).unwrap());
// "inactive"
let status = Status::Archived;
println!("{}", serde_json::to_string(&status).unwrap());
// "archived"
}rename_all applies to enum variants, with individual rename overrides.
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
enum Event {
// Variant names: renamed by rename_all
UserCreated {
user_id: u32, // becomes "userId"
user_name: String, // becomes "userName"
},
// Individual rename on variant
#[serde(rename = "userDeleted")]
UserRemoved {
user_id: u32,
},
// Individual renames on fields within variant
UserUpdated {
user_id: u32,
#[serde(rename = "name")]
user_name: String, // becomes "name", not "userName"
},
}
fn enum_with_fields() {
let event = Event::UserCreated {
user_id: 1,
user_name: "Alice".to_string(),
};
println!("{}", serde_json::to_string(&event).unwrap());
// {"userCreated":{"userId":1,"userName":"Alice"}}
let event = Event::UserRemoved { user_id: 1 };
println!("{}", serde_json::to_string(&event).unwrap());
// {"userDeleted":{"userId":1}}
let event = Event::UserUpdated {
user_id: 1,
user_name: "Bob".to_string(),
};
println!("{}", serde_json::to_string(&event).unwrap());
// {"userUpdated":{"userId":1,"name":"Bob"}}
}rename_all affects both variant names and field names within variants.
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct Settings {
theme: String,
font_size: u32,
#[serde(rename = "lang")]
language: String,
}
fn deserialization_example() {
// camelCase field names work for deserialization
let json = r#"{"theme":"dark","fontSize":14,"lang":"en"}"#;
let settings: Settings = serde_json::from_str(json).unwrap();
println!("{:?}", settings);
// Settings { theme: "dark", font_size: 14, language: "en" }
// Individual rename creates the expected JSON field name
// "lang" maps to the "language" field
}rename_all and rename affect both serialization and deserialization.
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct ComplexExample {
// rename_all applies
user_name: String,
// Individual rename overrides rename_all
#[serde(rename = "uid")]
user_id: u32,
// Skip doesn't interfere with rename
#[serde(skip)]
internal_id: u32,
// rename + default works together
#[serde(rename = "isAdmin", default)]
is_admin: bool,
// serialize_only changes behavior
#[serde(rename = "createdAt", serialize_only = "timestamp")]
created_at: u64,
}
fn complex_example() {
let data = ComplexExample {
user_name: "Alice".to_string(),
user_id: 42,
internal_id: 999,
is_admin: false,
created_at: 1234567890,
};
let json = serde_json::to_string(&data).unwrap();
println!("{}", json);
// {"userName":"Alice","uid":42,"isAdmin":false,"createdAt":1234567890}
// Note: internal_id is skipped (not in JSON)
}rename_all combines cleanly with other serde attributes.
use serde::{Serialize, Deserialize};
// Container-level rename_all affects all fields
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct Container {
field_one: String,
field_two: String,
}
// No container-level, individual renames only
#[derive(Serialize)]
struct IndividualOnly {
#[serde(rename = "fieldOne")]
field_one: String,
#[serde(rename = "fieldTwo")]
field_two: String,
}
// Mix: container-level + individual override
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct Mixed {
field_one: String, // Uses camelCase -> fieldOne
#[serde(rename = "customField")]
field_two: String, // Uses rename -> customField
field_three: String, // Uses camelCase -> fieldThree
}
fn container_vs_field() {
let c = Container { field_one: "a".to_string(), field_two: "b".to_string() };
println!("{}", serde_json::to_string(&c).unwrap());
// {"fieldOne":"a","fieldTwo":"b"}
let i = IndividualOnly { field_one: "a".to_string(), field_two: "b".to_string() };
println!("{}", serde_json::to_string(&i).unwrap());
// {"fieldOne":"a","fieldTwo":"b"}
let m = Mixed { field_one: "a".to_string(), field_two: "b".to_string(), field_three: "c".to_string() };
println!("{}", serde_json::to_string(&m).unwrap());
// {"fieldOne":"a","customField":"b","fieldThree":"c"}
}Container-level rename_all applies a default; field-level rename provides exceptions.
use serde::{Serialize, Deserialize};
// Converting Rust naming to match an external API
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
struct ExternalApiRequest {
// Standard snake_case conversion
user_id: u32,
created_at: u64,
// Exception: external API uses non-standard name
#[serde(rename = "accessToken")]
access_token: String,
// Exception: reserved word in external system
#[serde(rename = "type")]
request_type: String,
// Exception: abbreviation that shouldn't be snake_case
#[serde(rename = "id")]
identifier: String,
}
fn api_request_example() {
let request = ExternalApiRequest {
user_id: 123,
created_at: 1234567890,
access_token: "secret".to_string(),
request_type: "create".to_string(),
identifier: "abc".to_string(),
};
let json = serde_json::to_string(&request).unwrap();
println!("{}", json);
// {"user_id":123,"created_at":1234567890,"accessToken":"secret","type":"create","id":"abc"}
}Use rename_all for the common case and rename for exceptions that don't match the pattern.
| Scenario | Result |
|----------|--------|
| rename_all only | All fields transformed by the rule |
| rename_all + rename on field | Field uses rename, others use rename_all |
| Multiple fields with rename | Each rename applies independently |
| rename_all on enum | Variant names transformed |
| rename_all on enum with fields | Both variant and field names transformed |
| Field with rename in enum variant | Field rename overrides rename_all |
The interaction between rename_all and rename follows a clear precedence model:
rename_all establishes a default transformation: All fields or variants in the container receive the specified case conversion (snake_case, camelCase, etc.) when serialized or deserialized.
rename provides field-specific overrides: Any field or variant with an explicit rename attribute uses that exact name instead of applying the rename_all transformation.
The composition is straightforward:
rename_all to determine what the name would berename is present, use that name insteadrename on one field doesn't affect other fieldsKey insight: This design allows you to write idiomatic Rust code using snake_case field names while matching external naming conventions like camelCase for JSON APIs, with minimal boilerplate. The rename_all attribute handles the common case across dozens of fields, and individual rename attributes handle edge cases where the external name doesn't follow the patternāreserved words, abbreviations, legacy names, or API inconsistencies. This keeps struct definitions clean while ensuring exact control over serialized names.