serde:根据另一个字段的值反序列化一个字段

serde: deserialize a field based on the value of another field

我想反序列化一个有线格式,像这样 JSON,到下面的 Data 结构中,但我没有为相应的 rust 类型编写 serde Deserialize 实现.

{ "type": "TypeA", "value": { "id": "blah", "content": "0xa1b.." } }
enum Content {
   TypeA(Vec<u8>),
   TypeB(BigInt), 
}

struct Value {
    id: String,
    content: Content,
}

struct Data {
   typ: String,
   value: Value,
}

困难在于选择 Content 枚举的正确值,它基于 typ 值。 据我所知,serde 中的反序列化是无状态的,因此没有办法

如何使用 serde 实现这一点?

我看过

现有的 SO 答案通常利用相关字段处于同一级别这一事实。这里不是这种情况:实际数据模型很大,很深,字段“相距甚远”

更简单 使用 tagging,但改变你的数据结构:

use serde::{Deserialize, Deserializer}; // 1.0.130
use serde_json; // 1.0.67

#[derive(Debug, Deserialize)]
#[serde(tag = "type", content = "value")]
enum Data {
   TypeA(Value<String>),
   TypeB(Value<u32>), 
}

#[derive(Debug, Deserialize)]
struct Value<T> {
    id: String,
    content: T,
}



fn main() {
    let input = r#"{"type": "TypeA", "value": { "id": "blah", "content": "0xa1b..."}}"#;
    let data: Data = serde_json::from_str(input).unwrap();
    println!("{:?}", data);
}

Playground

此外,您可以使用一些中介编写自己的自定义反序列化器 serde_json::Value:

use serde::{Deserialize, Deserializer};// 1.0.130
use serde_json; // 1.0.67

#[derive(Debug)]
enum Content {
   TypeA(String),
   TypeB(String), 
}

#[derive(Debug)]
struct Value {
    id: String,
    content: Content,
}

#[derive(Debug)]
struct Data {
   typ: String,
   value: Value,
}


impl<'de> Deserialize<'de> for Data {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let json: serde_json::value::Value = serde_json::value::Value::deserialize(deserializer)?;
        let typ = json.get("type").expect("type").as_str().unwrap();
        let value = json.get("value").expect("value");
        
        let id = value.get("id").expect("id").as_str().unwrap();
        let content = value.get("content").expect("content").as_str().unwrap();
        
        Ok(Data {
            typ: typ.to_string(),
            value: Value {
                id: id.to_string(),
                content: {
                    match typ {
                        "TypeA" => Content::TypeA(content.to_string()),
                        "TypeB" => Content::TypeB(content.to_string()),
                        _ => panic!("Invalid type, but this should be an error not a panic"),
                    }
                }
            }
        })    
    }
}

fn main() {
    let input = r#"{"type": "TypeA", "value": { "id": "blah", "content": "0xa1b..."}}"#;
    let data: Data = serde_json::from_str(input).unwrap();
    println!("{:?}", data);
}

Playground

免责声明:我没有正确处理错误,您也可以将内容匹配提取到一个函数中。上面的代码只是为了说明主要思想。

DeserializeSeed 可以与正常的 Deserialize 代码混合。它不需要用于所有类型。这里,用它反序列化Value.

就够了

Playground

use serde::de::{DeserializeSeed, IgnoredAny, MapAccess, Visitor};
use serde::*;
use std::fmt;

#[derive(Debug)]
enum ContentType {
    A,
    B,
    Unknown,
}

#[derive(Debug)]
enum Content {
    TypeA(String),
    TypeB(i32),
}

#[derive(Debug)]
struct Value {
    id: String,
    content: Content,
}

#[derive(Debug)]
struct Data {
    typ: String,
    value: Value,
}

impl<'de> Deserialize<'de> for Data {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        struct DataVisitor;

        impl<'de> Visitor<'de> for DataVisitor {
            type Value = Data;

            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                formatter.write_str("struct Data")
            }

            fn visit_map<A>(self, mut access: A) -> Result<Self::Value, A::Error>
            where
                A: MapAccess<'de>,
            {
                let mut typ = None;
                let mut value = None;

                while let Some(key) = access.next_key()? {
                    match key {
                        "type" => {
                            typ = Some(access.next_value()?);
                        }
                        "value" => {
                            let seed = match typ.as_deref() {
                                Some("TypeA") => ContentType::A,
                                Some("TypeB") => ContentType::B,
                                _ => ContentType::Unknown,
                            };
                            value = Some(access.next_value_seed(seed)?);
                        }
                        _ => {
                            access.next_value::<IgnoredAny>()?;
                        }
                    }
                }

                Ok(Data {
                    typ: typ.unwrap(),
                    value: value.unwrap(),
                })
            }
        }

        deserializer.deserialize_map(DataVisitor)
    }
}

impl<'de> DeserializeSeed<'de> for ContentType {
    type Value = Value;

    fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
    where
        D: Deserializer<'de>,
    {
        struct ValueVisitor(ContentType);

        impl<'de> Visitor<'de> for ValueVisitor {
            type Value = Value;

            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                formatter.write_str("struct Value")
            }

            fn visit_map<A>(self, mut access: A) -> Result<Self::Value, A::Error>
            where
                A: MapAccess<'de>,
            {
                let mut id = None;
                let mut content = None;

                while let Some(key) = access.next_key()? {
                    match key {
                        "id" => {
                            id = Some(access.next_value()?);
                        }
                        "content" => {
                            content = Some(match self.0 {
                                ContentType::A => Content::TypeA(access.next_value()?),
                                ContentType::B => Content::TypeB(access.next_value()?),
                                ContentType::Unknown => {
                                    panic!("Should not happen if type happens to occur before value, but JSON is unordered.");
                                }
                            });
                        }
                        _ => {
                            access.next_value::<IgnoredAny>()?;
                        }
                    }
                }

                Ok(Value {
                    id: id.unwrap(),
                    content: content.unwrap(),
                })
            }
        }

        deserializer.deserialize_map(ValueVisitor(self))
    }
}

fn main() {
    let j = r#"{"type": "TypeA", "value": {"id": "blah", "content": "0xa1b.."}}"#;
    dbg!(serde_json::from_str::<Data>(j).unwrap());
    let j = r#"{"type": "TypeB", "value": {"id": "blah", "content": 666}}"#;
    dbg!(serde_json::from_str::<Data>(j).unwrap());
    let j = r#"{"type": "TypeB", "value": {"id": "blah", "content": "Foobar"}}"#;
    dbg!(serde_json::from_str::<Data>(j).unwrap_err());
}

此解决方案的主要缺点是您失去了推导代码的可能性。

有几种不同的方法可以解决这个问题,例如使用自定义 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 时,它将始终 return 它可以反序列化到的第一个成功变体(如果有的话)。因此,如果它可以反序列化一个 Vec<u8>,那么它总是会产生 Content::TypeA()。知道了这一点,那么在我们的check_type()中,如果ContentTypeTypeBContentTypeA。那我们就简单的改成TypeB.

impl Content {
    // TODO: impl proper error type instead of `String`
    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 {
    // TODO: impl proper error type instead of `String`
    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,
        })
    }
}

就是这样。现在我们可以针对以下内容对其进行测试:

// serde = { version = "1", features = ["derive"] }
// serde_json = "1.0"

use std::convert::TryFrom;

use serde::Deserialize;

// ... all the previous code ...

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);
}

然后它正确地导致 Datas Content::TypeAContent::TypeBContent::TypeC 和最后一个 Content::TypeD.


最后。有 issue #939 谈到添加一个 #[serde(validate = "...")]。但是,它是2017年创建的,所以我不会屏住呼吸。