Search code examples
rustserdeserde-json

Get serde_json to recognise this enum representation?


I have JSON data that I can parse with serde, looking essentially like this (greatly simplified!):

{
    "id": 1,
    "object": "Type1",
    "data": {
        "type_1_name": "value_1"
    }
}
{
    "id": 2,
    "object": "Type2",
    "data": {
        "type_2_name": "value_2"
    }
}

And here's the code I'm using to parse that:

#![allow(unused)]

use serde::Deserialize;
use serde_json::json;

#[derive(Deserialize)]
struct Item {
    id: u32,
    object: String,

    //#[serde(tag = "object")]
    data: Data,
}

#[derive(Deserialize)]
#[serde(untagged)]
//#[serde(tag = "object")]
//#[serde(tag = "object", content = "data")]
enum Data {
    Type1(Type1),
    Type2(Type2),
}

#[derive(Deserialize)]
struct Type1 {
    type_1_name: String,
}

#[derive(Deserialize)]
struct Type2 {
    type_2_name: String,
}

fn main() {
    let json = json!([
        {
            "id": 1,
            "object": "Type1",
            "data": {
                "type_1_name": "value_1"
            }
        },
        {
            "id": 2,
            "object": "Type2",
            "data": {
                "type_2_name": "value_2"
            }
        }
    ]);

    let _json: Vec<Item> = serde_json::from_value(json).unwrap();
}

See also in the rust playground.

When I parse it, I want the top-level data structures to be of the same type (Data in the example) because they have common fields, and it'll be useful to iterate over them as such.

I can successfully get serde to parse the lower-level value types (Type1/Type2) if I use #[serde(untagged)]. However, on a large data set it can be difficult to find errors if the error is just "no match" - I have the object field that tells me what type the data field should be, so I'm hoping I can tell serde to use this to know which one it should be, but the attempts I've made (commented-out tags in the example) don't work for various reasons.

Anyone know how to tell serde that it knows the type of the data, but the information is "one level up" from it?

(See also the serde docs)


Solution

  • You nearly had it:

    • Don't consume the object field in Item
    • flatten data into Item
    #[derive(Deserialize, Debug)]
    struct Item {
        id: u32,
        #[serde(flatten)]
        data: Data,
    }
    
    #[derive(Deserialize, Debug)]
    #[serde(tag = "object", content = "data")]
    enum Data {
        Type1(Type1),
        Type2(Type2),
    }
    

    Playground