【问题标题】:Deserialize value that may be an array of strings or a constant string?反序列化可能是字符串数组或常量字符串的值?
【发布时间】:2021-12-28 09:21:36
【问题描述】:

我正在使用 Serde 反序列化一些 JSON。我遇到的问题通常是一个字符串数组,但也可以是常量字符串"all"。用 JSON 模式表示,它看起来像这样:

{
    "oneOf": [
        {
            "const": "all" 
        },
        {
            "type": "array",
            "items": {
                "type": "string"
            }
        }
    ]
}

我想把它序列化到这个枚举中:

enum MyList {
    List(Vec<String>),
    All
}

这里有几个例子:

["foo", "bar"]  // Should be MyList::List
"all"           // Should be MyList::All
"foo"           // Should be an error!

问题是,我如何使用 serde 反序列化这个枚举? container attributesvariant attributes 似乎都没有帮助。如果需要,我可能会更改枚举的设计。但是,JSON 的结构超出了我的控制范围。

【问题讨论】:

    标签: rust enums deserialization serde


    【解决方案1】:

    untagged enum representationdeserialize_with variant attribute 的组合是可能的:

    use serde::{Deserialize, Deserializer};
    
    #[derive(Debug, Deserialize)]
    // This annotation "flattens" the enum, allowing one to treat a list of strings
    // directly as a List variant, without extra annotations.
    // Serde will attempt to deserialize input as each variant in order,
    // returning an error if no one matches.
    #[serde(untagged)]
    enum MyList {
        // This annotation delegates deserialization of this variant to own code,
        // since otherwise Serde wouldn't know what to do with the string.
        #[serde(deserialize_with = "all")]
        All,
        // This variant is deserialized as usual.
        List(Vec<String>),
    }
    
    // A custom deserialization function, referred to by `deserialize_with`.
    // (reference: https://github.com/serde-rs/serde/issues/1158)
    fn all<'de, D>(deserializer: D) -> Result<(), D::Error>
    where
        D: Deserializer<'de>,
    {
        #[derive(Deserialize)]
        // This enum is, by default, "externally tagged";
        // but, since it consists only of a single unit variant,
        // it means that it can be deserialized only from
        // the corresponding constant string - and that's exactly what we need
        enum Helper {
            #[serde(rename = "all")]
            Variant,
        }
        // We're not interested in the deserialized value (we know what it is),
        // so we can simply map it to (), as required by signature
        Helper::deserialize(deserializer).map(|_| ())
    }
    
    fn main() {
        // Trying to parse an array with both variants...
        let json = r#"["all", ["a", "b", "c"]]"#;
        let lists: Vec<MyList> = serde_json::from_str(json).expect("cannot parse");
        // ...we can see that it is indeed parsed correctly...
        println!("{:?}", lists);
    
        // ...and any unexpected string results in an error
        serde_json::from_str::<MyList>(r#""foo""#).unwrap_err();
    }
    

    Playground

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-11-29
      • 2023-03-04
      • 2021-05-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-03
      • 1970-01-01
      相关资源
      最近更新 更多