You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rust新手求教:如何为Serde实现自定义rename_all属性?

Answers to Your Rust Serde Questions

Q1: What is #[serde(rename_all = "...")], is it a macro rule, and where is it implemented in Serde's source?

#[serde(rename_all = "...")] is a container-level attribute provided by Serde to handle bulk renaming of struct/enum fields during serialization/deserialization. For example, rename_all = "snake_case" tells Serde to automatically map struct fields in camelCase to snake_case in JSON (or vice versa for deserialization).

It's not a standalone macro rule—it's an attribute annotation recognized by Serde's derive procedural macros (specifically #[derive(Serialize, Deserialize)]). When you use these derive macros, Serde parses the #[serde(...)] attributes at compile time and generates the corresponding serialization/deserialization code tailored to your renaming rules.

As for where to find the implementation in Serde's source code:

  • Attribute parsing logic: Look in the serde_derive crate's src/attr.rs file. This is where Serde defines how to interpret parameters like rename_all from the #[serde] attribute.
  • Core naming conversion logic: The Case enum (which includes built-in styles like SnakeCase, CamelCase) and its associated conversion functions live in serde crate's src/rename.rs. This is where the actual string transformation for rename_all happens.
  • Code generation for derive macros: The serde_derive crate's src/internals directory (e.g., ast.rs for abstract syntax tree definitions, codegen.rs for generating the final ser/de code) uses the parsed attributes to generate the right code for your structs/enums.

Q2: How to implement a custom container attribute to deserialize Title Case JSON to snake_case structs?

Serde doesn't support Title Case (words capitalized, separated by spaces) out of the box for rename_all, but there are a few practical ways to solve this:

serde_with is a popular Serde extension that adds support for custom naming transformations. Here's how to use it:

First, add the dependencies to your Cargo.toml:

serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_with = "3.0"

Then define your conversion function and apply the attribute:

use serde::Deserialize;
use serde_with::RenameAllWith;

// Convert Title Case strings to snake_case
fn title_case_to_snake_case(input: &str) -> String {
    input
        .to_lowercase()
        .replace(' ', "_")
        .trim_matches('_')
        .replace("__", "_") // Handle multiple spaces if present
}

#[derive(Deserialize, Debug)]
#[serde_with::serde_as]
#[serde_with::rename_all_with(title_case_to_snake_case)]
struct Vehicle {
    car_prop: i32,
    door_prop: i32,
}

fn main() -> Result<(), serde_json::Error> {
    let json = r#"{"Car Prop": 1, "Door Prop": 2}"#;
    let vehicle: Vehicle = serde_json::from_str(json)?;
    println!("{:?}", vehicle); // Output: Vehicle { car_prop: 1, door_prop: 2 }
    Ok(())
}

Option 2: Manually implement the Deserialize trait (no extra dependencies)

If you don't want to add a third-party crate, you can hand-write the Deserialize implementation to map Title Case keys to your snake_case fields:

use serde::Deserialize;
use serde_json::Value;

#[derive(Debug)]
struct Vehicle {
    car_prop: i32,
    door_prop: i32,
}

// Helper to convert Title Case to snake_case
fn title_to_snake(s: &str) -> String {
    s.split_whitespace()
        .map(|word| word.to_lowercase())
        .collect::<Vec<_>>()
        .join("_")
}

impl<'de> Deserialize<'de> for Vehicle {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let raw_obj = Value::deserialize(deserializer)?;
        let obj = raw_obj.as_object()
            .ok_or_else(|| serde::de::Error::custom("expected a JSON object"))?;

        // Map each Title Case JSON key to the corresponding struct field
        let car_prop = obj.get("Car Prop")
            .ok_or_else(|| serde::de::Error::missing_field("Car Prop"))?
            .deserialize()?;
        let door_prop = obj.get("Door Prop")
            .ok_or_else(|| serde::de::Error::missing_field("Door Prop"))?
            .deserialize()?;

        Ok(Vehicle { car_prop, door_prop })
    }
}

fn main() -> Result<(), serde_json::Error> {
    let json = r#"{"Car Prop": 1, "Door Prop": 2}"#;
    let vehicle: Vehicle = serde_json::from_str(json)?;
    println!("{:?}", vehicle);
    Ok(())
}

Option 3: Write a custom derive macro (for large-scale use)

If you have many structs needing this conversion, you can build a custom procedural macro to automate the process. This requires learning Rust's proc-macro system, but the basic steps are:

  1. Create a new proc-macro crate.
  2. Parse the struct's abstract syntax tree (AST) to extract field names.
  3. Generate Deserialize implementation code that maps each snake_case field to its Title Case JSON key.
  4. Use your custom #[derive(TitleCaseDeserialize)] attribute in your main code.

This is more complex but scales well for multiple structs.


内容的提问来源于stack exchange,提问作者Nona

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.07 17:02:29