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

Bru*_*der 6 rust serde

我想将有线格式(如 JSON)反序列化为下面的结构,但我无法为相应的 rust 类型Data编写 serde实现。Deserialize

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

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

struct Data {
   typ: String,
   value: Value,
}
Run Code Online (Sandbox Code Playgroud)

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

  • typ知道反序列化时的值是什么content(即使反序列化顺序是有保证的)
  • 或者将 的值注入typ解串器然后收集它。

如何使用 serde 来实现这一点?

我看过

  • serde_state但我无法让宏工作,并且这个库正在包装 serde,这让我担心
  • DeserializeSeed但我的理解是它必须用来代替Deserialize所有类型,并且我的数据模型很大

现有的 SO 答案通常利用相关领域处于同一级别的事实。这里的情况并非如此:实际的数据模型很大、很深,而且字段“相距很远”

Net*_*ave 8

使用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);
}
Run Code Online (Sandbox Code Playgroud)

操场

另外,您可以使用一些中介编写自己的自定义反序列化器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);
}
Run Code Online (Sandbox Code Playgroud)

操场

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


Val*_*tin 6

有几种不同的方法可以解决这个问题,例如使用 custom impl Deserialize for Data,然后反序列化为 a serde_json::Value,然后在类型之间手动调整。

作为一个例子,请查看我过去写的这个答案。它不是一对一的解决方案,但它可能会Deserialize根据您的需要提供一些手动实施的提示。


话虽如此。就我个人而言,我更喜欢尽量减少必须impl Deserialize手动进行的操作,而是反序列化为另一种类型,并使用#[serde(from = "FromType")].

首先,type_: String我建议我们引入 ,而不是enum ContentType

#[derive(Deserialize, Clone, Copy, Debug)]
enum ContentType {
    TypeA,
    TypeB,
    TypeC,
    TypeD,
}
Run Code Online (Sandbox Code Playgroud)

现在,让我们考虑一下您介绍的类型。正如您提到的,我已经添加了一些额外的变体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,
}
Run Code Online (Sandbox Code Playgroud)

还没有什么疯狂的事情,也没有什么不同。所有的“魔法”都发生在IntermediateData类型中,以及impl TryFrom.


首先,我们来介绍 a check_type(),它接受 aContentType并根据 进行检查Content。如果Content变体与变体不匹配ContentType,则进行转换。

简而言之,当使用#[serde(untagged)]then 时,当 serde 尝试反序列化时,Content它将始终返回它可以反序列化的第一个成功变体(如果有)。因此,如果它可以反序列化 a Vec<u8>,那么它总是会产生Content::TypeA(). 知道了这一点,那么在我们的check_type(), if the ContentTypeisTypeB和 the Contentis TypeA。然后我们只需将其更改为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
            )),
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在我们需要的是中间IntermediateData以及TryFrom调用.check_type()Content

#[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,
        })
    }
}
Run Code Online (Sandbox Code Playgroud)

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

// 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);
}
Run Code Online (Sandbox Code Playgroud)

然后它会正确地生成带有、、和最后一个的Datas 。Content::TypeAContent::TypeBContent::TypeCContent::TypeD


最后。有问题 #939讨论添加#[serde(validate = "...")]. 不过,它是 2017 年创建的,所以我不会屏息以待。