Search code examples
rustserde-json

Deserializing mixed data types with Rust's serde_json


I'm trying to de-serialize JSON into a struct in Rust using the serde_json crate. An example JSON entry:

{
    "name": "example",
    "stuff": [
        "item 1",
        "item 2",
        [
            "nested item 1",
            "nested item 2",
            "nested item 3"
        ]
    ]
}

The "stuff" field contains an array of elements that can be either a string or an array of strings. I'm trying to deserialize this kind of data into a Struct.

I'm attempting to use the following Enum and Struct combo to deserialize with serde_json.from_str():

#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(untagged)]
enum ListOrNestedList {
    List(Vec<String>),
    NestedList(Vec<Vec<String>>),
}

#[derive(Debug, Serialize, Deserialize, Clone)]
struct Entry {
    name: String,
    stuff: ListOrNestedList,
}

Attempting to deserialize this produces the following panic:

called `Result::unwrap()` on an `Err` value: Error("data did not match any variant of untagged enum ListOrNestedList", line: 123, column: 40)

Based on the docs, using an Enum is one possible way to handle JSON containing different possible types. However, in my example, BOTH possible types are contained in the "stuff" field. Is there a different approach needed for deserializing such data, or am I simply making a mistake?


Solution

  • You're putting the enum at the wrong level.

    [
            "item 1",
            "item 2",
            [
                "nested item 1",
                "nested item 2",
                "nested item 3"
            ]
        ]
    

    is not a "list or nested list", since it contains both strings and vectors of strings at the same time. A "list or nested list" would be all one or all the other.

    What you need to parse is:

    #[derive(Debug, Serialize, Deserialize, Clone)]
    #[serde(untagged)]
    enum StringOrVec {
        String(String),
        Vec(Vec<String>),
    }
    
    #[derive(Debug, Serialize, Deserialize, Clone)]
    struct Entry {
        name: String,
        stuff: Vec<StringOrVec>,
    }
    

    https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=e74b0152bad114743d18ae2c6141aa58