Can Serde deserialize JSON to one of a set of types depending on the value of a field?

与世无争的帅哥 提交于 2019-12-12 15:07:22

问题


I have a group of different messages that come in as JSON and can be distinguished based on a single field, but then each variant has a different collection of secondary fields:

#[derive(Debug, Serialize, Deserialize)]
struct MessageOne {
    ///op will always be "one"
    op: String,
    x: f64,
    y: f64,
}

#[derive(Debug, Serialize, Deserialize)]
struct MessageTwo {
    ///op will always be "two"
    op: String,
    a: f64,
    b: i64,
}

The different message types are routed to different processing functions (e.g. process_message_one, process_message_two, etc). Is there an elegant or idiomatic way to automatically select the correct message sub-type? Currently I've defined a generic message:

#[derive(Debug, Serialize, Deserialize)]
struct MessageGeneric {
    op: String,
}

then parse the incoming JSON into the MessageGeneric, read the op field and then deserialize again, matching on op to select the correct message type. Full example:

#![allow(unused)]

extern crate serde; // 1.0.78
extern crate serde_json; // 1.0.27

#[macro_use]
extern crate serde_derive;

use std::collections::HashMap;

#[derive(Debug, Serialize, Deserialize)]
struct MessageGeneric {
    op: String,
}

#[derive(Debug, Serialize, Deserialize)]
struct MessageOne {
    ///op will always be "one"
    op: String,
    x: f64,
    y: f64,
}

#[derive(Debug, Serialize, Deserialize)]
struct MessageTwo {
    ///op will always be "two"
    op: String,
    a: f64,
    b: f64,
}

fn process_message_one(m: &MessageOne) {
    println!("Processing a MessageOne: {:?}", m);
}

fn process_message_two(m: &MessageTwo) {
    println!("Processing a MessageTwo: {:?}", m);
}



fn main() {
    let data = r#"{
        "op": "one",
        "x": 1.0,
        "y": 2.0
    }"#;

    let z: MessageGeneric = serde_json::from_str(data).unwrap();

    match z.op.as_ref() {
        "one" => {
            let zp: MessageOne = serde_json::from_str(data).unwrap();
            process_message_one(&zp);
        },
        "two" => {
            let zp: MessageTwo = serde_json::from_str(data).unwrap();
            process_message_two(&zp);
        },
        _ => println!("Unknown Message Type")

    }

}

I've seen Serde's enum representations but it was unclear to me if/how that would be applied in this case. The messages coming in are defined by an external API, so I can't control their content beyond knowing what the variants are.


回答1:


There is no point to keep "one" or "two" in your structure MessageOne and MessageTwo: if you have constructed this structure you already know if it is message one or message two.

extern crate serde; // 1.0.78
extern crate serde_json; // 1.0.27

#[macro_use]
extern crate serde_derive;

#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "op")]
enum Message {
    #[serde(rename = "one")]
    One { x: f64, y: f64 },
    #[serde(rename = "two")]
    Two { a: f64, b: f64 },
}

fn process_message(message: &Message) {
    println!("Processing a : {:?}", message);
}

use serde_json::Error;

fn main() -> Result<(), Error> {
    let data = r#"{
        "op": "one",
        "x": 1.0,
        "y": 2.0
    }"#;

    let message: Message = serde_json::from_str(data)?;
    process_message(&message);

    let data = r#"{
        "op": "two",
        "a": 1.0,
        "b": 2.0
    }"#;

    let message: Message = serde_json::from_str(data)?;
    process_message(&message);

    let data = r#"{
        "op": "42",
        "i": 1.0,
        "j": 2.0
    }"#;

    let message: Message = serde_json::from_str(data)?;
    process_message(&message);
    Ok(())
}
Standard Output
Processing a : One { x: 1.0, y: 2.0 }
Processing a : Two { a: 1.0, b: 2.0 }

Standard Error
Error: Error("unknown variant `42`, expected `one` or `two`", line: 2, column: 18)


来源:https://stackoverflow.com/questions/53018165/can-serde-deserialize-json-to-one-of-a-set-of-types-depending-on-the-value-of-a

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!