Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Conditionally decoding JSON based on a field in the JSON

Tags:

json

rust

serde

I am receiving JSON from an API and the response can be one of 30 types. Each type has a unique set of fields, but all responses have a field type which states which type it is.

My approach is to use serde. I create a struct for each response type and make them decodable. Once I have that how do I choose which struct should be used for a freshly received message?

At the moment, I've created another struct TypeStruct with only a single field for type. I decode the JSON into a TypeStruct, then choose the appropriate struct for received message, based on type value, and decode the message again.

I would like to get rid of this decoding duplication.

like image 306
eyeinthebrick Avatar asked Oct 24 '15 22:10

eyeinthebrick


1 Answers

You can use the existing enum deserialization. I'll give a step by step example to deserialize your format to the following enum:

#[derive(Debug, PartialEq, Eq, Deserialize)]
enum MyType {
    A {gar: ()},
    B {test: i32},
    C {blub: String},
}
  1. Start with an example json string:

    let json = r#"{"type": "B", "test": 42}"#;
    
  2. Turn it into a Value enum

    let mut json: serde_json::Value = serde_json::from_str(json).unwrap();
    
  3. Rip out the type field

    let type_ = {
        let obj = json.as_object_mut().expect("object");
        let type_ = obj.remove("type").expect("`type` field");
        if let serde_json::Value::String(s) = type_ {
            s
        } else {
            panic!("type field not a string");
        }
    };
    
  4. Create the "proper" enum json. A struct with a single field where the name of the field is the enum variant and the value of the field is the variant value

    let mut enum_obj = std::collections::BTreeMap::new();
    enum_obj.insert(type_, json);
    let json = serde_json::Value::Object(enum_obj);
    
  5. Use the generated json deserializer to turn the json into a value of your enum

    let obj: MyType = serde_json::from_value(json).unwrap();
    
like image 51
oli_obk Avatar answered Oct 05 '22 01:10

oli_obk