How to deserialize a JSON file which contains null values using Serde?

给你一囗甜甜゛ 提交于 2020-03-13 06:21:26

问题


I want to deserialize the chemical elements JSON file from Bowserinator on github using Serde. For this I created a structure with all the needed fields and derived the needed macros:

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Element {
    name: String,
    appearance: String,
    atomic_mass: f64,
    boil: f64, 
    category: String,
    #[serde(default)]
    color: String,
    density: f64,
    discovered_by: String,
    melt: f64, 
    #[serde(default)]
    molar_heat: f64,
    named_by: String,
    number: String,
    period: u32,
    phase: String,
    source: String,
    spectral_img: String,
    summary: String,
    symbol: String,
    xpos: u32,
    ypos: u32,
}

This works fine until it gets to fields which contain a "null" value. E.g. for the field "color": null, in Helium.

The error message I get is { code: Message("invalid type: unit value, expected a string"), line: 8, column: 17 } for this field.

I experimented with the #[serde(default)] Macro. But this only works when the field is missing in the JSON file, not when there is a null value.

I like to do the deserialization with the standard macros avoiding to program a Visitor Trait. Is there a trick I miss?


回答1:


A deserialization error occurs because the struct definition is incompatible with the incoming objects: the color field can also be null, as well as a string, yet giving this field the type String forces your program to always expect a string. This is the default behaviour, which makes sense. Be reminded that String (or other containers such as Box) are not "nullable" in Rust. As for a null value not triggering the default value instead, that is just how Serde works: if the object field wasn't there, it would work because you have added the default field attribute. On the other hand, a field "color" with the value null is not equivalent to no field at all.

One way to solve this is to adjust our application's specification to accept null | string, as specified by @user25064's answer:

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Element {
    color: Option<String>,
}

Playground with minimal example

Another way is to write our own deserialization routine for the field, which will accept null and turn it to something else of type String. This can be done with the attribute #[serde(deserialize_with=...)].

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Element {
    #[serde(deserialize_with="parse_color")]
    color: String,
}

fn parse_color<'de, D>(d: D) -> Result<String, D::Error> where D: Deserializer<'de> {
    Deserialize::deserialize(d)
        .map(|x: Option<_>| {
            x.unwrap_or("black".to_string())
        })
}

Playground




回答2:


Any field that can be null should be an Option type so that you can handle the null case. Something like this?

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Element {
    ...
    color: Option<String>,
    ...
}


来源:https://stackoverflow.com/questions/44205435/how-to-deserialize-a-json-file-which-contains-null-values-using-serde

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!