Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

When a certain property isn't there it provides a empty object, with all its fields as empty strings instead of excluding the value. 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"
            }
        }
    ]
}
like image 938
XAMPPRocky Avatar asked Jun 16 '16 22:06

XAMPPRocky


1 Answers

In str_or_u64, we use an untagged enum to represent either a string or a number. We can then deserialize the field into that enum and convert it to a number.

We annotate the two fields in Points using deserialize_with to tell it to use our special conversion:

use serde::{Deserialize, Deserializer}; // 1.0.124
use serde_json; // 1.0.64

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

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

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

fn str_or_u64<'de, D>(deserializer: D) -> Result<u64, D::Error>
where
    D: Deserializer<'de>,
{
    #[derive(Deserialize)]
    #[serde(untagged)]
    enum StrOrU64<'a> {
        Str(&'a str),
        U64(u64),
    }

    Ok(match StrOrU64::deserialize(deserializer)? {
        StrOrU64::Str(v) => v.parse().unwrap_or(0), // Ignoring parsing errors
        StrOrU64::U64(v) => v,
    })
}

fn main() {
    let input = r#"{
        "foo":[
            {
                "points":{
                    "x":"",
                    "y":"",
                    "name":""
                }
            },
            {
                "points":{
                    "x":78,
                    "y":92,
                    "name":"bar"
                }
            }
        ]
    }"#;

    dbg!(serde_json::from_str::<WebResponse>(input));
}

See also:

  • How to transform fields during deserialization using Serde?
like image 93
Shepmaster Avatar answered Sep 28 '22 01:09

Shepmaster