Может ли Serde десериализовать JSON до одного из набора типов в зависимости от значения поля?

У меня есть группа разных сообщений, которые поступают как JSON и могут быть выделены на основе одного поля, но тогда каждый вариант имеет различную коллекцию вторичных полей:

#[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,
}

Различные типы сообщений направляются различным функциям обработки (например, process_message_one, process_message_two и т. Д.). Есть ли элегантный или идиоматический способ автоматического выбора правильного подтипа сообщения? В настоящее время я определил общее сообщение:

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

затем проанализируйте входящий JSON в MessageGeneric, прочтите поле op и затем снова десериализуйте, сопоставив op, чтобы выбрать правильный тип сообщения. Полный пример:

#![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")

    }

}

Я видел представления перечисления Serde, но мне было неясно, будет ли / как это применяться в Это дело. Входящие сообщения определяются внешним API, поэтому я не могу контролировать их содержимое, не зная, какие есть варианты.


person JoshAdel    schedule 27.10.2018    source источник


Ответы (1)


Нет смысла сохранять «один» или «два» в вашей структуре MessageOne и MessageTwo: если вы построили эту структуру, вы уже знаете, является ли это сообщением первое или сообщение два.

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)
person Stargateur    schedule 27.10.2018
comment
Что, если структуры MessageOne, MessageTwo и т. Д. Определены в стороннем крейте с полем op? Можно ли создать enum, который собирает их вместе, а затем анализирует до нужного? - person JoshAdel; 27.10.2018
comment
@JoshAdel Я не понимаю вашей проблемы, просто создайте немаркированное перечисление play.rust-lang.org/. Но ваш вопрос не касается вашей реальной проблемы. Например, если эти структуры определены где-то еще, почему они являются производными Serialize и Deserialize? Если у вас все еще есть проблема, задайте другой вопрос, который точно описывает ваш случай, или зайдите в чат ржавчины. - person Stargateur; 28.10.2018