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

Serde: overwriting child struct's #[serde(rename_all = "camelCase")]

我有一个 child 和 parent 结构,如下所示:

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

#[serde(rename_all = "snake_case")]
pub struct Parent {
    child: Child,
}

我希望他们都是snake_case

child 结构是在导入的库中定义的,所以我无法更改它,我不想复制它。

是否可以取消/覆盖 child 结构顶部的 camelCase 宏?在父母之上添加 snake_case 似乎不起作用。

我认为这不可能通过一些不错的属性来实现。


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

  1. 您可以通过 serialize_with:
  2. 告诉 serde 使用您的自定义函数来序列化成员
#[derive(Debug,Serialize)]
#[serde(rename_all = "snake_case")]
pub struct Parent {
    #[serde(serialize_with = "serialize_rename")]
    child: Child,
}
  1. 然后您可以实现自定义序列化功能以重定向到自定义 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))
}
  1. 你为什么要这么做?因为序列化程序本质上只是被序列化的数据结构的访问者。所以当你碰巧“访问”一个结构时,你可以重定向到一个不同的方法来序列化结构字段:
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
}
  1. 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()
    }
}
  1. 现在,如果您注意了(我没有注意),您会注意到我们需要将键和值转发到 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();
}
  1. 最后可以转换struct field keys的大小写了:
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::*;
            let key: &str = Box::leak(Box::new(key.to_case(Case::Snake)));
            e.insert(key);
            key
        },
    }
};
self.0.serialize_field(key, value)

我遗漏了相当多的样板文件,所以这是一个可运行的文件 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)
}

Playground