使用Serde反序列化具有多个类型的字段的JSON

5

我有一些JSON文本数据,其中的字段可能是字符串或字符串数组。以下是四个可能的示例:

{
        "keya": "some string",
        "keyb": "some string"
}


{
        "keya": "some string",
        "keyb": ["some string", "some string"]
}

{
        "keya": ["some string", "some string"],
        "keyb": "some string"
}

{
        "keya": ["some string", "some string"],
        "keyb": ["some string", "some string"]
}

我该如何创建一个类型,使我能够使用Serde反序列化这样的JSON文本数据?

这是一个什么样的示例?您的JSON是否可以具有任何结构,还是仅限于此处列出的选项? - Holloway
1个回答

6

这个答案早于问题补充的额外要求,但作为单独的问题保留原样。


处理尾随逗号

由于每个闭合括号 } 前存在尾随逗号,提供的输入数据不是有效的 JSON 数据。如果您必须使用尾随逗号,则传统的 serde_json 包不适合您的需求,您可能需要将所有使用 serde_json 的地方替换为支持尾随逗号的包,例如 json5json5 提供了类似于 serde_json 的 API,因此以下答案仍然有效。

处理可以具有多种类型的字段

可以使用带有 #[serde(untagged)] 属性的 enum 来处理具有多种可能值类型的 JSON 字段,该枚举包含一个 String 或一个 Vec<String>。有关属性的详细信息,请参见 serde 的官方文档中的枚举表示形式

完整示例:

use serde::{Serialize, Deserialize};

#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
enum StringOrStringVec {
    String(String),
    Vec(Vec<String>)
}

#[derive(Debug, Serialize, Deserialize)]
struct MyObj {
    keya: StringOrStringVec,
    keyb: StringOrStringVec,
}

fn main() {
    let input_json = r#"
        {
            "keya": "some string",
            "keyb": ["some string", "some string"]
        }
    "#;
    let my_obj: MyObj = serde_json::from_str(input_json).unwrap();
    println!("{:?}", my_obj);
    
    let input_json = r#"
        {
            "keya": ["some string", "some string"],
            "keyb": "some string"
        }
    "#;
    let my_obj: MyObj = serde_json::from_str(input_json).unwrap();
    println!("{:?}", my_obj);
}

示例输出:

MyObj { keya: String("some string"), keyb: Vec(["some string", "some string"]) }
MyObj { keya: Vec(["some string", "some string"]), keyb: String("some string") }

See it in action on Rust Playground


嗨,尾随逗号是一个错误,我已经将它们删除了,你可能想要编辑你的答案吗? - Happy Machine
谢谢上面的回复。我的问题是我并不总是知道键名是什么 - 这与GraphQL有关。 - Happy Machine
然后使用HashMap<String,StringOrStringVec>或者简单地使用serde_json::Value代替MyObj - 另外,这是一个独立的问题。 - Caesar
1
我将保留这个答案,因为不同的键名使其成为一个不同的问题,应该单独提出。这个答案中枚举和属性部分仍然适用,正如@Caesar所说,使用HashMap是一个潜在的解决方案。 - kotatsuyaki
如果我按照StringOrStringVec示例操作,当结果是一个数组时,我就无法迭代它。即使我知道它是Vec<Str>(因为它是graphql,所以我可以检查类型),我该如何实现to_iter? - Happy Machine
使用match枚举来处理StringVec<String>的情况。如果您可以确保StringOrStringVec类型的值是变体StringOrStringVec::Vec,那么您可以在另一个永远无法到达的match分支上引发panic! - kotatsuyaki

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