How do I deserialize into trait, not a concrete type?

主宰稳场 提交于 2019-11-29 06:27:02

It looks like you fell into the same trap that I fell into when I moved from C++ to Rust. Trying to use polymorphism to model a fixed set of variants of a type. Rust's enums (similar to Haskell's enums, and equivalent to Ada's variant record types) are different from classical enums in other languages, because the enum variants can have fields of their own.

I suggest you change your code to

#[derive(Debug, Serialize, Deserialize)]
enum Contract {
    Bar { data: Vec<Foo> },
    Baz { data: Vec<Foo>, tag: String },
}

#[derive(Debug, Serialize, Deserialize)]
struct Foo {
    x: u32,
    y: u32,
}

impl Contract {
    fn do_something(&self) {
        match *self {
            Contract::Bar { ref data } => println!("I'm a Bar and this is my data {:?}", data),
            Contract::Baz { ref data, ref tag } => {
                println!("I'm Baz {} and this is my data {:?}", tag, data)
            }
        }
    }
}

Adding on to oli_obk's answer, you can use Serde's enum representation to distinguish between the types.

Here, I use the internally-tagged representation to deserialize these two similar objects into the appropriate variant:

{
  "driver": "file",
  "path": "/var/log/foo"
}
{
  "driver": "http",
  "port": 8080,
  "endpoint": "/api/bar"
}
use serde; // 1.0.82
use serde_derive::*; // 1.0.82
use serde_json; // 1.0.33

#[derive(Debug, Deserialize, PartialEq)]
#[serde(tag = "driver")]
enum Driver {
    #[serde(rename = "file")]
    File { path: String },
    #[serde(rename = "http")]
    Http { port: u16, endpoint: String }
}

fn main() {
    let f = r#"   
{
  "driver": "file",
  "path": "/var/log/foo"
}
"#;

    let h = r#"
{
  "driver": "http",
  "port": 8080,
  "endpoint": "/api/bar"
}
"#;

    let f: Driver = serde_json::from_str(f).unwrap();
    assert_eq!(f, Driver::File { path: "/var/log/foo".into() });

    let h: Driver = serde_json::from_str(h).unwrap();
    assert_eq!(h, Driver::Http { port: 8080, endpoint: "/api/bar".into() });
}

You don't have to squash it all into one enum, you can create separate types as well:

#[derive(Debug, Deserialize, PartialEq)]
#[serde(tag = "driver")]
enum Driver {
    #[serde(rename = "file")]
    File(File),
    #[serde(rename = "http")]
    Http(Http),
}

#[derive(Debug, Deserialize, PartialEq)]
struct File {
    path: String,
}

#[derive(Debug, Deserialize, PartialEq)]
struct Http {
    port: u16,
    endpoint: String,
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!