[serde] Deserialize JSON string using type description

Hello Community!

I would like to ask if there is an option to do the following:

  • I provide an arbitrary JSON string at runtime.
  • The descriptor is also known when the application is starting.
  • I would like the serde to deserialize the message into a MessageValue given the supplied MessageDescriptor data.

It means we should get MessageValue { ... "some_int64_field": Int64(700)... } instead of MessageValue { ... "some_int64_field": Int32(700) ... } in the example code below.

Is it possible to do this by extending the behavior of the handlers that serde framework provides?

Thank you in advance!

use serde; // 1.0.130
use serde::{Serialize, Deserialize};
use serde_json; // 1.0.69
use std::collections::HashMap;

// Serde code will automatically map my value either to Int32 or Int64
#[derive(Debug, Serialize, Deserialize)]
struct MessageValue {
    #[serde(flatten)]
    data: HashMap<String, Value>
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
enum Value {
    Int32(i32),
    Int64(i64),
}


// But I'd like to set value type based on message descriptor data, which are unknown at compilation time
#[derive(Debug, Serialize, Deserialize)]
struct MessageDescriptor {
    #[serde(flatten)]
    data: HashMap<String, String>
}
impl MessageDescriptor {
    // This method will implement the logic of value type casting
    fn convert_value(&self, field: &str, val: Value) -> Value {
        let value_type = self.data.get(field).unwrap().as_ref();
        match value_type {
            "int64" => match val {
                Value::Int32(v) => Value::Int64(v as i64),
                _ => val
            }
            _ => val
        }
    }
}


fn main() {
    let msg = r#"{
        "some_int32_field": 23,
        "some_int64_field": 700
    }"#;
    let msg_deserialized: MessageValue = serde_json::from_str(msg)
        .expect("deserialize the message");
        
    println!("msg_deserialized {:?}", msg_deserialized);
    // Expected output:
    // msg_deserialized MessageValue { data: {"some_int32_field": Int32(23), "some_int64_field": Int32(700)} }
    
    
    // I would like to be able to provide some descriptor in runtime to deserialize the value to needed type. 
    let msg_descriptor = r#"{
        "some_int32_field": "int32",
        "some_int64_field": "int64"
    }"#;
    let msg_descriptor_deserialized: MessageDescriptor = serde_json::from_str(msg_descriptor)
        .expect("deserialize the message descriptor");
    println!("msg_descriptor {:?}", msg_descriptor_deserialized);
    // Expected output:
    // msg_descriptor MessageDescriptor { data: {"some_int32_field": "int32", "some_int64_field": "int64"} }
    
}

(Playground)

Output:

msg_deserialized MessageValue { data: {"some_int32_field": Int32(23), "some_int64_field": Int32(700)} }
msg_descriptor MessageDescriptor { data: {"some_int64_field": "int64", "some_int32_field": "int32"} }

You are probably looking for DeserializeSeed. You can implement this trait on MessageDescriptor to guide the deserialization process in which types are expected.

Here is a rough version how it can look like: Rust Playground

As a sidenote: I would avoid using flatten if not necessary, since it can have some unintended side effects. For the two cases you can replace it with transparent.

2 Likes

This is really what I need!
Thanks for sharing an example, very helpful!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.