我有一个孩子和父母的结构,如下所示:
#[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似乎不起作用。
我不认为通过一些好的属性可以实现这一点。
在一般情况下这是可行的,但我真的认为你不应该:
您可以告诉 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)
然后,您可以实现自定义序列化函数以重定向到自定义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)
为什么要这么做?因为序列化器本质上只是被序列化的数据结构的访问者。因此,当您碰巧“访问”结构体时,您可以重定向到不同的方法来序列化结构体字段:
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)
这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)
现在,如果您注意过(我没有),您会注意到我们需要将键和值转发到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)
最后,您可以转换结构体字段键的大小写:
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)