Rabbid76
Rabbid76

Reputation: 210878

Can I map types when deserializing with "Serde"?

Basically I have the following data structure:

enum Value {
    Scalar(f64),
    Vector3((f64, f64, f64)),
}

struct Data {
    attribute: Value,
}

Serializing with serde/serde_json gives the following

{
    "attribute": { "Scalar": 1.0 }  
}
{
    "attribute": { "Vector3": [ 1.0, 2.0, 3.0 ] }
}

Deserialize works as intended. However, is it possible to deserialize the following to the same data structure?

{
    "attribute": 1.0  
}
{
    "attribute": [ 1.0, 2.0, 3.0 ]
}

Is it possible to "map" f64 to Scalar(f64) and Vec<f64> to Vector3((f64, f64, f64))?


Both forms should work. It would be nice if the following minimal example worked:

[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
enum Value {
    Scalar(f64),
    Vector3((f64, f64, f64)),
}

#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
struct Data {
    attribute: Value,
}

fn main() {
    let js1 = r#"
    {
        "attribute": { "Scalar": 1.0 }  
    }"#;
    let d1: Data = serde_json::from_str(js1).unwrap();
    println!("{:?}", d1);

    let js2 = r#"
    {
        "attribute": { "Vector3": [ 1.0, 2.0, 3.0 ] }
    }"#;
    let d2: Data = serde_json::from_str(js2).unwrap();
    println!("{:?}", d2);

    let js3 = r#"
    {
        "attribute": 1.0  
    }"#;
    let d3: serde_json::Result<Data> = serde_json::from_str(js3);
    match d3 {
        Ok(d3) => println!("{:?}", d3),
        Err(e) => println!("{:?}", e),
    }

    let js4 = r#"
    {
        "attribute": [ 1.0, 2.0, 3.0 ] 
    }"#;
    let d4: serde_json::Result<Data> = serde_json::from_str(js4);
    match d4 {
        Ok(d4) => println!("{:?}", d4),
        Err(e) => println!("{:?}", e),
    }
}

Output:

Data { attribute: Scalar(1.0) }
Data { attribute: Vector3((1.0, 2.0, 3.0)) }
Error("expected value", line: 3, column: 22)
Error("expected value", line: 3, column: 22)

Upvotes: 3

Views: 419

Answers (2)

Rabbid76
Rabbid76

Reputation: 210878

@Raspberry1111 presented a well-welcomed workaround. However, a more direct solution is still welcome.


I had to use #[serde (untagged)] (see Enum representations) and to change the data structure to meet all of my needs:

#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
enum TaggedValue {
    Scalar(f64),
    Vector3((f64, f64, f64)),
}

#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
#[serde(untagged)]
enum Value {
    Scalar(f64),
    Vector3((f64, f64, f64)),
    Tagged(TaggedValue),
}

#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
struct Data {
    attribute: Value,
}

// [...]

Output:

Data { attribute: Tagged(Scalar(1.0)) }
Data { attribute: Tagged(Vector3((1.0, 2.0, 3.0))) }
Data { attribute: Scalar(1.0) }
Data { attribute: Vector3((1.0, 2.0, 3.0)) }

Any more direct solution is still welcome

Upvotes: 1

Raspberry1111
Raspberry1111

Reputation: 78

You can use #[serde(untagged)] on your enum to allow it to be "mapped". Serde doesn't currently support both at the same time but there is a hack here although I haven't tried it. You could also have an enum with the tagged name inside of the variant

Example:

#[serde(untagged)]
enum Value {
    Scalar(f64),
    ScalarTagged {Scalar: f64},
    Vector3((f64, f64, f64)),
    Vector3Tagged {
        Vector3: (f64, f64, f64)
    }
}

This passes your minimal example

Upvotes: 1

Related Questions