Listerone
Listerone

Reputation: 1611

How to conditionally deserialize JSON to two different variants of an enum?

Let's say I have JSON data like the following:

{
  "type": "A",
  "value": [ 1, 2, 3, 4, 5 ]
}
{
  "type": "B",
  "value": [ [ 1, 2, 3, 4, 5 ], [ 6, 7, 8 ] ]
}

type determines the type of value, which in the first example is Vec<u32> and in the second is Vec<Vec<u32>>.

If I represent the above data as follows:

enum DataValue {
  TypeA(Vec<u32>),
  TypeB(Vec<Vec<u32>>)
}

struct Data {
  data_type: String,
  value: DataValue
}

How do I implement serde deserialization to properly decode these values?

Upvotes: 6

Views: 2878

Answers (3)

Sven Marnach
Sven Marnach

Reputation: 602115

You can deserialize your JSON data directly to an instance of DataValue if you give Serde enough information to know how to do this:

#[derive(Debug, Deserialize)]
#[serde(tag = "type", content = "value")]
enum DataValue {
    #[serde(rename = "A")]
    TypeA(Vec<u32>),
    #[serde(rename = "B")]
    TypeB(Vec<Vec<u32>>),
}

let data_a = r#"
    {
        "type": "A",
        "value": [1, 2, 3, 4, 5]
    }"#;
let a: DataValue = serde_json::from_str(data_a)?;

Playground

If you name your enum variants A and B, you can omit the #[serde(rename = "…")] attributes.

This way of serializing enums is called "adjacent tagging". You can learn about the various options of tagging enums in the Serde documentation on enum serialization.

Your Data struct contains a redundant additional tag data_type. This information is already encoded in the enum, so I don't think you need this. If you need this information as a string, you can add a method to the enum:

impl DataValue {
    fn variant_name(&self) -> &'static str {
        match self {
            DataValue::TypeA(_) => "A",
            DataValue::TypeB(_) => "B",
        }
    }
}

Upvotes: 10

edwardw
edwardw

Reputation: 14002

Fortunately serde has build-in support for enum type:

//# serde = { version = "1.0.99", features = ["derive"] }
//# serde_json = "1.0.40"

use serde::Deserialize;

#[derive(Deserialize, Debug)]
#[serde(tag = "type")]
enum Data {
    A { value: Vec<u32> },
    B { value: Vec<Vec<u32>> },
}

fn main() {
    let a: Data = serde_json::from_str(r#"{"type": "A", "value": [ 1, 2, 3, 4, 5 ]}"#).unwrap();
    let b: Data =
        serde_json::from_str(r#"{"type": "B", "value": [[1, 2, 3, 4, 5], [6, 7, 8 ]]}"#).unwrap();

    println!("{:?}", a);
    println!("{:?}", b);
}

Upvotes: 5

tricknology
tricknology

Reputation: 1148

This may be a person opinion but I would generally try to avoid serialization / deserialization on enums.

Would this not be close to the same thing in C++?

struct DataValue final{
  TypeA(Vec<u32>) final,
  TypeB(Vec<Vec<u32>>) final
}

Upvotes: -7

Related Questions