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
似乎不起作用。
我认为这不可能通过一些不错的属性来实现。
一般情况下是可行的,但我真的认为你不应该:
- 您可以通过
serialize_with
: 告诉 serde 使用您的自定义函数来序列化成员
#[derive(Debug,Serialize)]
#[serde(rename_all = "snake_case")]
pub struct Parent {
#[serde(serialize_with = "serialize_rename")]
child: Child,
}
- 然后您可以实现自定义序列化功能以重定向到自定义
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))
}
- 你为什么要这么做?因为序列化程序本质上只是被序列化的数据结构的访问者。所以当你碰巧“访问”一个结构时,你可以重定向到一个不同的方法来序列化结构字段:
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
}
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()
}
}
- 现在,如果您注意了(我没有注意),您会注意到我们需要将键和值转发到
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();
}
- 最后可以转换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)
}
我有一个 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
似乎不起作用。
我认为这不可能通过一些不错的属性来实现。
一般情况下是可行的,但我真的认为你不应该:
- 您可以通过
serialize_with
: 告诉 serde 使用您的自定义函数来序列化成员
#[derive(Debug,Serialize)]
#[serde(rename_all = "snake_case")]
pub struct Parent {
#[serde(serialize_with = "serialize_rename")]
child: Child,
}
- 然后您可以实现自定义序列化功能以重定向到自定义
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))
}
- 你为什么要这么做?因为序列化程序本质上只是被序列化的数据结构的访问者。所以当你碰巧“访问”一个结构时,你可以重定向到一个不同的方法来序列化结构字段:
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
}
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()
}
}
- 现在,如果您注意了(我没有注意),您会注意到我们需要将键和值转发到
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();
}
- 最后可以转换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)
}