使用 serde 进行两种(反)序列化格式。

11
我已成功使用serde_json来反序列化和序列化JSON。我的设置看起来有点像这样(非常简化):
use serde::{Deserialize, Serialize};
use serde_json;
use serde_with::skip_serializing_none;

#[skip_serializing_none]
#[derive(Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
struct Foo {
    #[serde(flatten)]
    bar: Option<Bar>,
    
    baz_quux: Option<u8>,
}

#[skip_serializing_none]
#[derive(Deserialize, Serialize)]
struct Bar {
    #[serde(rename = "plughXyzzySomeRandomStuff")]
    plugh_xyzzy: Option<u8>
}

然后我在Foo上实现了FromStrDisplay,它们分别调用serde_json::from_strserde_json::to_string,以便轻松(反)序列化结构体。

然而,我现在想使用serde_ini来支持(反)序列化INI文件到同一Rust数据结构。但我实在搞不清楚该怎么做。

结构本身很简单,但我的具体问题在于属性:

  • JSON和INI格式中的键名不同(JSON格式使用传统的驼峰命名法,而INI则不是),因此我必须以其他方式解决#[serde(rename)]#[serde(rename_all)]属性的问题,但我不确定在哪里或如何解决。
  • #[serde(flatten)]serde_ini的所有字符串值似乎不兼容,这些值需要一个#[serde(deserialize_with="from_str)]"属性来处理所有非字符串值,但这显然只适用于INI值而不是JSON值。

总之,我想要做的就是重新实现这些属性,或根据使用的(De)Serializer有条件地使用它们,但我不知道该如何做到。


我了解 #[serde(alias)] 可以用于从多种格式反序列化,但不能用于序列化。 - tobiasvl
1
不可能的,如果不使用相同的键,则两个格式规范是不兼容的。这应该实现为两个结构。 - Stargateur
这是我去年一直在研究的内容;最终,我遵循了 @dtolnay 的建议,通过线程本地传递上下文到 Serialize 中。具体可以参考 https://github.com/serde-rs/serde/pull/2005#pullrequestreview-617070896。 - eggyal
1个回答

13
这是serde设计的限制。意在将DeserializeSerialize实现与SerializerDeserializer实现分开,这样就可以方便地选择不同的格式并进行切换,从而带来了极大的灵活性和便利性。然而,这也意味着无法针对不同的格式单独微调您的DeserializeSerialize实现。
我以前做过的方法是复制数据类型,以便可以为每种格式进行配置,并提供零成本转换。

谢谢!这个想法也曾经在我脑海中出现过,很高兴它并不完全是疯狂的(我只是希望有一种更优雅的方式)。但是,提供两个结构体之间零成本转换的惯用方法是什么呢? - tobiasvl
假设结构体具有相同的布局,我认为编译器只能保证它们是repr(C),我建议使用union - eggyal
2
@tobiasvl 实现自 and 以后,Rust 就足够聪明了。 - Stargateur
@Stargateur,这真的是零成本吗?有趣!最终我也是这样做的,那就太好了。虽然存在很多代码重复(基本上每个结构体和枚举类型都要有两个),但如果无法避免的话,我也可以接受。至少效果非常好!谢谢! - tobiasvl
@eggyal 我在尝试跟进这个问题时遇到了一些困难... 我理解得对吗?如果任何字段没有实现 Copy,那么这种方法将无法工作?这相当限制(没有 StringVec...) - undefined
1
@KyleCarow 不,那不正确。没有必要复制或克隆;通常你会以这样的方式编写,使数据被“移动”而不是复制。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接