Deserializing enum JSON field with Serde with multiple types


#1

I’m trying to parse some JSON with Serde that has a custom enum with an odd format. I’ve been reading lots on custom deserializers but can’t wrap my head around how I’d implement this. Maybe if someone showed me, I could actually start to understand the more generic case. The JSON I need to parse looks like:

{“upgrade”: true} // perform a generic upgrade
{“upgrade”: “force”} // Generic upgrade + force, note String vs. bool
{“upgrade”: “confirm”} // Confirm a previous upgrade.

I suppose I could also add support for {“upgrade”: false} as a no-op for consistency, though it’s never used. I don’t necessarily care that {“upgrade”: true} deserializes to a bool–an enum member which I can match against is perfectly fine. I really don’t want {“upgrade”: “true”}.

Here’s my code, using Rust nightly:

#![feature(custom_derive, plugin)]
#![plugin(serde_macros)]

use std::io;

extern crate serde;
extern crate serde_json;

#[derive(Deserialize, Debug)]
enum UpgradeField {
    True,
    Force,
    Confirm
}

#[derive(Deserialize, Debug)]
struct Data {
    upgrade: UpgradeField,
}

fn main() {
    let mut input = String::new();
    io::stdin().read_line(&mut input)
        .expect("Failed to read input");
    let data: Data = serde_json::from_str(&input)
        .expect("Failed to parse input");
    println!("{:?}", data);
}

Actually, I’m not sure why typing {“upgrade”: “force”} or {“upgrade”: “Force”} causes a panic with:

thread ‘’ panicked at ‘Failed to parse input: Syntax(“expected value”, 1, 13)’, …/src/libcore/result.rs:785

So how do I go about getting any of these values to parse, not to mention true vs. "force"/"confirm"? I assumed the values would match on the enum value name, or maybe lower-cased. But that doesn’t appear to happen.

Thanks.


#2

I guess you want something like this:

    #![feature(custom_derive, plugin)]
    #![plugin(serde_macros)]
    
    extern crate serde;
    extern crate serde_json as json;
    
    
    #[derive(Deserialize, Debug, PartialEq, Eq)]
    enum UpgradeField {
        True,
        Force,
        Confirm
    }
    
    #[derive(Deserialize, Debug, PartialEq, Eq)]
    struct Data {
        #[serde(deserialize_with="deserialize_upgrade_field")]
        upgrade: UpgradeField,
    }
    
    
    fn deserialize_upgrade_field<D>(de: &mut D) -> Result<UpgradeField, D::Error>
        where D: serde::Deserializer
    {
        let deser_result: json::Value = try!(serde::Deserialize::deserialize(de));
        match deser_result {
            json::Value::String(ref s) if &*s == "force" => Ok(UpgradeField::Force),
            json::Value::String(ref s) if &*s == "confirm" => Ok(UpgradeField::Confirm),
            json::Value::Bool(b) if b == true => Ok(UpgradeField::True),
            _ => Err(serde::de::Error::custom("Unexpected value")),
    
    
        }
    }
    
    fn main() {
        let input = r#"{"upgrade": true}"#;
        let data: Data = json::from_str(input).unwrap();
        println!("{:?}", &data);
        assert_eq!(data.upgrade, UpgradeField::True);
    
        let input = r#"{"upgrade": "force"}"#;
        let data: Data = json::from_str(input).unwrap();
        println!("{:?}", &data);
        assert_eq!(data.upgrade, UpgradeField::Force);
    
        let input = r#"{"upgrade": "confirm"}"#;
        let data: Data = json::from_str(input).unwrap();
        println!("{:?}", &data);
        assert_eq!(data.upgrade, UpgradeField::Confirm);
    
    }   

#3

This is great, thanks! I have a number of questions about this code, but they’re slowly going away as I read through the book.

But here’s something I don’t get. Not that this issue exists in my project, but say I wanted to suddenly deserialize UpgradeField to XML. Or say I have a crate containing datatypes for a generic API but no serialization/deserialization logic, and maybe I want to implement the deserialization logic in another crate. How would I go about offering multiple data formats, or is that not supported? Or do both formats have to be side-by-side in the same implementation? I was perpetually looking for a JSON-specific way to implement deserialization when it seems I should have been looking at the generic Deserializer.

Thanks again.


#4

I am not sure about xml support. There is serde_xml but it looks a bit limited. So, you might need to implement your own custom generic Serializer/Deserializer which is not trivial but there are some examples in serde documentation.

How should the xml look like for the Data struct above?