mikechambers
mikechambers

Reputation: 3079

Lifting nested values from json that might be optional using Serde in Rust

I am using this solution:

Is there a way to omit wrapper/root objects when deserializing objects with Serde?

to raise float values out of nested json structures:

Here is the JSON

"activitiesWon": {
    "statId": "activitiesWon",
    "basic": {
        "value": 3.0,
        "displayValue": "3"
    }
},

And here is the data structure and customer deserializer:

#[derive(Serialize, Deserialize, Debug, Default, Clone, Copy)]
pub struct PvpStatsData {
    #[serde(rename = "activitiesWon", deserialize_with="property_to_float")]
    pub activities_won:f32,
}

pub fn property_to_float<'de, D>(deserializer: D) -> Result<f32, D::Error>
    where D: serde::de::Deserializer<'de>,
{
    #[derive(Deserialize)]
    struct Outer {
        pub basic: Inner,
    }
    
    #[derive(Deserialize)]
    struct Inner {
        pub value: f32,
    }

    let helper = Outer::deserialize(deserializer)?;
    Ok(helper.basic.value)
}

This works great. However, some of the structures / properties might not exist, and thus their field signature looks like this, with the custom deserializer which either returns a Option, or None:

#[derive(Serialize, Deserialize, Debug, Default, Clone, Copy)]
pub struct PvpStatsData {
    #[serde(rename = "bestSingleGameKills", deserialize_with="property_to_option_float")]
    pub best_single_game_kills:Option<f32>,
}

pub fn property_to_option_float<'de, D>(deserializer: D) -> Result<Option<f32>, D::Error>
    where D: serde::de::Deserializer<'de>,
{
    println!("PARSER");
    #[derive(Deserialize, Debug, )]
    struct Outer {
        pub basic: Inner,
    }
    
    #[derive(Deserialize, Debug, )]
    struct Inner {
        pub value: f32,
    }

    Option::<Outer>::deserialize(deserializer).map(|o:Option<Outer>| match o {
        Some(e) => {
            println!("{:?}", e);
            Some(e.basic.value)
        },
        None => None,
    })
}

However, when parsing, if the property doesn't exist in json, then I get a parse error:

serde_json::Error : Error("missing field `bestSingleGameKills`", line: 1, column: 4358)

and my custom de-serialization method is never called.

Anyone know why the de-serialization method is not called? and / or how I can get it to be called when the property doesn't exist? I have other deserializers that can deal with Optional results, but I suspect this has something to do with the combination of the nested json and option.

Ive posted a playground of the example here: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=022d7ca3513e411644d186518d177645

(just uncomment the second json block to see the issue)

Upvotes: 1

Views: 2634

Answers (1)

georgemp
georgemp

Reputation: 790

The default field attribute should work (I'm assuming you are ok with best_single_game_kills defaulting to None when it is absent in the json).

#[derive(Deserialize, Debug, Default, Clone, Copy)]
pub struct PvpStatsData {
    #[serde(default, rename = "activitiesWon", deserialize_with="property_to_float")]
    pub activities_won:f32,
    
    #[serde(default, rename = "bestSingleGameKills", deserialize_with="property_to_option_float")]
    pub best_single_game_kills:Option<f32>,
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=a50748db2fb585a9430371a6ecd10f82

Upvotes: 2

Related Questions