Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

rust

serde

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.

like image 263
JoshAdel Avatar asked Oct 27 '18 02:10

JoshAdel


People also ask

How does rust Serde work?

Serde is a framework for serializing and deserializing Rust data structures efficiently and generically. The Serde ecosystem consists of data structures that know how to serialize and deserialize themselves along with data formats that know how to serialize and deserialize other things.

What is Serde JSON?

Serde JSON JSON is a ubiquitous open-standard format that uses human-readable text to transmit data objects consisting of key-value pairs. { "name": "John Doe", "age": 43, "address": { "street": "10 Downing Street", "city": "London" }, "phones": [ "+44 1234567", "+44 2345678" ] }

Is Serde fast?

Serde. Serde, the incumbent serialization/deserialization library, is elegant, flexible, fast to run, and slow to compile.

Is Serde slow?

Serde is really designed to be fast, by allowing fully statically dispatched operations without runtime reflections so formats and types are decoupled at code but transparent to the optimization. It's serde_json which cares all the weirdnesses of the JSON format.


1 Answers

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)
like image 194
Stargateur Avatar answered Nov 03 '22 19:11

Stargateur