6

Expanding on my previous question, how do you handle an array that contains mixed structs that are both valid? I've tried looking at the serde_json::Value source. However it doesn't handle the case of two different structs.

I can't simply merge them, and use Options over their properties as that would make the single struct unwieldy, and it is important for them to be distinct.

Rust structs

#[derive(Clone, Debug, Deserialize)]
struct WebResponse {
    foo: Vec<Structs>,
}

enum Structs {
    Foo(Foo),
    Bar(Bar),
}

#[derive(Clone, Debug, Deserialize)]
struct Foo {
    name: String,
    baz: Vec<String>,
}

#[derive(Clone, Debug, Deserialize)]
struct Bar {
    quux: u64
}

Example JSON

{
    "foo": [
        {
            "name": "John",
            "baz": ["Lorem", "Ipsum"]
        },
        {
            "quux": 17
        }
    ]
}
dtolnay
  • 9,621
  • 5
  • 41
  • 62
XAMPPRocky
  • 3,160
  • 5
  • 25
  • 45

1 Answers1

6

There's a few ways to solve this. The easiest, if you have few variants, is to simply implement Deserialize manually like so:

impl serde::de::Deserialize for Structs {
    fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error>
        where D: serde::Deserializer,
    {
        deserializer.deserialize(Visitor)
    }
}

struct Visitor;

impl serde::de::Visitor for Visitor {
    type Value = Structs;

    fn visit_map<V>(&mut self, mut visitor: V) -> Result<Structs, V::Error>
        where V: serde::de::MapVisitor,
    {
        let s: String = try!(visitor.visit_key()).expect("got struct with no fields");
        let val = match &s as &str {
            "name" => {
                Ok(Structs::Foo(Foo {
                    name: try!(visitor.visit_value()),
                    baz: {
                        let s: String = try!(visitor.visit_key()).expect("baz field");
                        assert_eq!(&s, "baz");
                        try!(visitor.visit_value())
                    },
                }))
            },
            "baz" => {
                Ok(Structs::Foo(Foo {
                    baz: try!(visitor.visit_value()),
                    name: {
                        let s: String = try!(visitor.visit_key()).expect("name field");
                        assert_eq!(&s, "name");
                        try!(visitor.visit_value())
                    },
                }))
            },
            "quux" => {
                Ok(Structs::Bar(Bar {
                    quux: try!(visitor.visit_value())
                }))
            },
            other => panic!("no struct has field `{}`", other),
        };
        try!(visitor.end());
        val
    }
}

The problem with this implementation is that it obviously doesn't scale. What you can do instead, is to create a new Deserializer that you give the first field name that was found and override the deserialize_map method to process the various structs through a custom MapVisitor.

If you feel that this is a common case supported by other serialization frameworks, feel free to post a bug report on the serde repository or the serde-json repository. I'm sure there's a way to automatically generate such an implementation, but it's not trivial for sure.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
oli_obk
  • 28,729
  • 6
  • 82
  • 98