Aaronepower Aaronepower - 5 months ago 5
JSON Question

Convert two types into a single type with Serde

I'm writing for a program that hooks into a web service which sends back JSON.

My problem is that when a certain property isn't there instead of excluding the value, it provides a empty object, with all it's fields as empty strings. However when the property exists some of the properties are

u64
, how can I have it so
serde
handles this case?

Rust Structs



#[derive(Clone, Debug, Deserialize)]
struct WebResponse {
foo: Vec<Foo>,
}

#[derive(Clone, Debug, Deserialize)]
struct Foo {
points: Points,
}

#[derive(Clone, Debug, Deserialize)]
struct Points {
x: u64,
y: u64,
name: String,
}


Example JSON



{
"foo": [
{
"points": {
"x": "",
"y": "",
"name": ""
}
},
{
"points": {
"x": 78,
"y": 92,
"name": "bar"
}
}
]
}

Answer

Serde supports an interesting selection of annotations that can be used to customize the serialization or deserialization for a type while still using the derived implementation for the most part.

In your case, you need to be able to decode a field that can be specified as one of multiple types, and you don't need information from other fields to decide how to decode the problematic fields. The #[serde(deserialize_with="$path")] annotation is well suited to solve your problem.

We need to define a function that will decode either an empty string or an integer value into an u64. We can use the same function for both fields, since we need the same behavior. This function will use a custom Visitor to be able to handle both strings and integers. It's a bit long, but it makes you appreciate all the work that Serde is doing for you!

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

extern crate serde;
extern crate serde_json;

use serde::{de, Deserializer};

#[derive(Clone, Debug, Deserialize)]
struct WebResponse {
    foo: Vec<Foo>,
}

#[derive(Clone, Debug, Deserialize)]
struct Foo {
    points: Points,
}

#[derive(Clone, Debug, Deserialize)]
struct Points {
    #[serde(deserialize_with="deserialize_u64_or_empty_string")]
    x: u64,
    #[serde(deserialize_with="deserialize_u64_or_empty_string")]
    y: u64,
    name: String,
}

struct DeserializeU64OrEmptyStringVisitor;

impl de::Visitor for DeserializeU64OrEmptyStringVisitor {
    type Value = u64;

    fn visit_u64<E>(&mut self, v: u64) -> Result<Self::Value, E>
        where E: de::Error
    {
        Ok(v)
    }

    fn visit_str<E>(&mut self, v: &str) -> Result<Self::Value, E>
        where E: de::Error
    {
        if v == "" {
            Ok(0)
        } else {
            Err(E::invalid_value("got a non-empty string"))
        }
    }
}

fn deserialize_u64_or_empty_string<D>(deserializer: &mut D) -> Result<u64, D::Error>
    where D: Deserializer
{
    deserializer.deserialize(DeserializeU64OrEmptyStringVisitor)
}

fn main() {
    let value = serde_json::from_str::<WebResponse>(r#"{
    "foo": [
        {
            "points": {
                "x": "",
                "y": "",
                "name": ""
            }
        },
        {
            "points": {
                "x": 78,
                "y": 92,
                "name": "bar"
            }
        }
    ]
}"#);
    println!("{:?}", value);
}

Cargo.toml:

[dependencies]
serde = "0.7"
serde_json = "0.7"
serde_macros = "0.7"
Comments