有几种不同的方法可以解决这个问题,例如使用自定义的impl Deserialize for Data
,然后将其反序列化为serde_json::Value
,再手动在类型之间进行转换。
有一个相对应的示例,可以参考我之前写的回答。它不是一一对应的解决方案,但可能会给出手动实现Deserialize
的提示,以满足你的需求。
话虽如此,个人认为当必须手动impl Deserialize
时,最好尽量减少并反序列化到另一个类型中,然后使用#[serde(from = "FromType")]
进行自动转换。
首先,建议我们引入enum ContentType
,而不是type_: String
。
#[derive(Deserialize, Clone, Copy, Debug)]
enum ContentType {
TypeA,
TypeB,
TypeC,
TypeD,
}
现在,让我们考虑您介绍的类型。我已经向
Content
添加了一些额外的变体,因为您提到变体可能不同。
#[derive(Deserialize, Debug)]
#[serde(untagged)]
enum Content {
TypeA(Vec<u8>),
TypeB(Vec<u8>),
TypeC(String),
TypeD { foo: i32, bar: i32 },
}
#[derive(Deserialize, Debug)]
struct Value {
id: String,
content: Content,
}
#[derive(Deserialize, Debug)]
#[serde(try_from = "IntermediateData")]
struct Data {
#[serde(alias = "type")]
type_: ContentType,
value: Value,
}
目前还没有太疯狂或者差别很大的内容。所有的“魔法”都在 IntermediateData
类型中发生,以及 impl TryFrom
。
首先,我们引入一个check_type()
函数,它接受一个ContentType
并将其与Content
进行比较。如果Content
变体与ContentType
变体不匹配,则将其转换。
简而言之,在使用#[serde(untagged)]
时,当Serde尝试反序列化Content
时,如果可能,它将始终返回第一个成功的变体,如果有的话。所以如果它可以反序列化一个Vec<u8>
,那么它将始终得到Content::TypeA()
。知道了这一点,那么在我们的check_type()
中,如果ContentType
是TypeB
,并且Content
是TypeA
,那么我们就将其简单地更改为TypeB
。
impl Content {
fn check_type(self, type_: ContentType) -> Result<Self, String> {
match (type_, self) {
(ContentType::TypeA, content @ Self::TypeA(_)) => Ok(content),
(ContentType::TypeB, Self::TypeA(content)) => Ok(Self::TypeB(content)),
(ContentType::TypeC | ContentType::TypeD, content) => Ok(content),
(type_, content) => Err(format!(
"unexpected combination of {:?} and {:?}",
type_, content
)),
}
}
}
现在我们需要中间数据
IntermediateData
和一个
TryFrom
转换,该转换会在
Content
上调用
check_type()
。
#[derive(Deserialize, Debug)]
struct IntermediateData {
#[serde(alias = "type")]
type_: ContentType,
value: Value,
}
impl TryFrom<IntermediateData> for Data {
type Error = String;
fn try_from(mut data: IntermediateData) -> Result<Self, Self::Error> {
data.value.content = data.value.content.check_type(data.type_)?;
Ok(Data {
type_: data.type_,
value: data.value,
})
}
}
就这些。现在我们可以根据以下内容来测试它:
use std::convert::TryFrom;
use serde::Deserialize;
fn main() {
let json = r#"{ "type": "TypeA", "value": { "id": "foo", "content": [0, 1, 2, 3] } }"#;
let data: Data = serde_json::from_str(json).unwrap();
println!("{:#?}", data);
let json = r#"{ "type": "TypeB", "value": { "id": "foo", "content": [0, 1, 2, 3] } }"#;
let data: Data = serde_json::from_str(json).unwrap();
println!("{:#?}", data);
let json = r#"{ "type": "TypeC", "value": { "id": "bar", "content": "foo" } }"#;
let data: Data = serde_json::from_str(json).unwrap();
println!("{:#?}", data);
let json = r#"{ "type": "TypeD", "value": { "id": "baz", "content": { "foo": 1, "bar": 2 } } }"#;
let data: Data = serde_json::from_str(json).unwrap();
println!("{:#?}", data);
}
然后,它将正确生成带有 Content::TypeA
、Content::TypeB
、Content::TypeC
和最后一个 Content::TypeD
的 Data
。
最后,有一个issue #939,它讨论了添加 #[serde(validate = "...")]
的问题。然而,它是在2017年创建的,所以不要抱太大希望。
Content
变体都保证是相同的吗?如果不是,你的实际格式是否更加复杂?因为如果不是,那么手动为Data
实现反序列化可能是一个简单的解决方案。 - vallentintyp
和content
之间有3个嵌套级别。 - Bruno Griederassert_eq!
以显示您想要的内容。不要省略您认为微不足道的细节。 - Stargateur