Serde:覆盖子结构的#[serde(rename_all = "camelCase")]

ilm*_*moi 1 rust serde

我有一个孩子和父母的结构,如下所示:

#[serde(rename_all = "camelCase")]
pub struct Child {
    some_field: u64,
}

#[serde(rename_all = "snake_case")]
pub struct Parent {
    child: Child,
}
Run Code Online (Sandbox Code Playgroud)

我希望他们两个都是snake_case

子结构是在导入的库中定义的,因此我无法更改它,而且我不想重复它。

是否可以无效/覆盖camelCase子结构顶部的宏?在父级之上添加snake_case似乎不起作用。

Cae*_*sar 6

我不认为通过一些好的属性可以实现这一点。


在一般情况下这是可行的,但我真的认为你不应该:

  1. 您可以告诉 serde 使用自定义函数来序列化成员serialize_with

    #[derive(Debug,Serialize)]
    #[serde(rename_all = "snake_case")]
    pub struct Parent {
        #[serde(serialize_with = "serialize_rename")]
        child: Child,
    }
    
    Run Code Online (Sandbox Code Playgroud)
  2. 然后,您可以实现自定义序列化函数以重定向到自定义Serializer.

    struct RenamingSerializer<S>(S);
    
    fn serialize_rename<T: Serialize, S: Serializer>(val: &T, ser: S) -> Result<S::Ok, S::Error> {
        val.serialize(RenamingSerializer(ser))
    }
    
    Run Code Online (Sandbox Code Playgroud)
  3. 为什么要这么做?因为序列化器本质上只是被序列化的数据结构的访问者。因此,当您碰巧“访问”结构体时,您可以重定向到不同的方法来序列化结构体字段:

    struct RenameSerializeStruct<S>(S);
    
    impl<S: Serializer> Serializer for RenamingSerializer<S> {
        type SerializeStruct = RenameSerializeStruct<S::SerializeStruct>;
        fn serialize_struct(
            self,
            name: &'static str,
            len: usize,
        ) -> Result<Self::SerializeStruct, Self::Error> {
            Ok(RenameSerializeStruct(self.0.serialize_struct(name, len)?))
        }
        // snip all the other stuff you need to impl
    }
    
    Run Code Online (Sandbox Code Playgroud)
  4. SerializeStruct是另一个“访问者”,它将看到结构体的所有字段。

    impl<S: SerializeStruct> SerializeStruct for RenameSerializeStruct<S> {
        type Ok = S::Ok;
        type Error = S::Error;
    
        fn serialize_field<T: ?Sized>(
            &mut self,
            key: &'static str,
            value: &T,
        ) -> Result<(), Self::Error>
        where T: Serialize {
            /// change key to the case you want, and call serialize_field on .0
        }
    
        fn end(self) -> Result<Self::Ok, Self::Error> {
            self.0.end()
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  5. 现在,如果您注意过(我没有),您会注意到我们需要将键和值转发到S::serialize_field,但这需要一个&'static str. 如果我们String在这里建造一个新的,它的寿命不会足够长。好吧,对此没有完美的解决方案,您可以使用池来内部化字符串,我决定将它们存储在映射中(因为所有关键字符串在编译时都存在,所以我不会担心它会变大):

    lazy_static::lazy_static! {
        // Probably not so good if your thing is multithreaded.
        // (There are of course further overengineered solutions for that.)
        static ref NAMES: Mutex<HashMap<&'static str, &'static str>> = Default::default();
    }
    
    Run Code Online (Sandbox Code Playgroud)
  6. 最后,您可以转换结构体字段键的大小写:

    let key = {
        let mut names = NAMES.lock().expect("poisoned and dead");
        use std::collections::hash_map::Entry::*;
        match names.entry(key) {
            Occupied(e) => *e.get(),
            Vacant(e) => {
                use convert_case::*; // use heck; might be better.
                let key: &str = Box::leak(Box::new(key.to_case(Case::Snake)));
                e.insert(key);
                key
            },
        }
    };
    self.0.serialize_field(key, value)
    
    Run Code Online (Sandbox Code Playgroud)

我省略了相当多的样板文件,所以这里有一个可运行的Playground


现在,上述解决方案不会递归地执行此操作,而仅针对一级。如果您想应用于所有后代,我认为可以通过定义一个包装器结构,对其进行标记#[serde(transparent, serialize_with = "serialize_rename")],并将所有值包装在调用中,就像serialize_field使用此包装器结构一样。


无论如何,如果您必须疯狂地寻求上述解决方案,或者想要至少处理数十个结构上的数百个字段。

如果您愿意专注于Child,有一个更明智的解决方案:

#[derive(Debug, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct Parent {
    #[serde(serialize_with = "serialize_rename")]
    child_field: Child,
}

fn serialize_rename<S: Serializer>(val: &Child, ser: S) -> Result<S::Ok, S::Error> {
    let Child { some_field } = val;
    let some_field = *some_field;
    #[derive(Serialize)]
    #[serde(rename_all = "snake_case")]
    struct SnakeChild {
        some_field: u64,
    }
    SnakeChild { some_field }.serialize(ser)
}
Run Code Online (Sandbox Code Playgroud)

操场