20

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?

4

3 回答 3

30

由于结构定义与传入的对象不兼容,会发生反序列化错误:该color字段也可以是null,也可以是字符串,但是为该字段指定类型String会强制您的程序始终期望字符串。这是默认行为,这是有道理的。请注意,String(或其他容器,例如Box)在 Rust 中不是“可空的”。至于null不触发默认值的值,这就是 Serde 的工作方式:如果对象字段不存在,它会起作用,因为您添加了默认字段属性。另一方面,带有值的字段“颜色”null不等于根本没有字段。

解决此问题的一种方法是将我们的应用程序规范调整为接受null | string,如@user25064 的回答所指定:

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

带有最小示例的游乐场

另一种方法是为该字段编写我们自己的反序列化例程,它将接受null并将其转换为其他类型的东西String。这可以通过属性来完成#[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())
        })
}

操场

也可以看看:

于 2017-05-26T22:18:04.177 回答
4

任何可以为 null 的字段都应该是一种Option类型,以便您可以处理 null 的情况。像这样的东西?

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Element {
    ...
    color: Option<String>,
    ...
}
于 2017-05-26T16:18:18.733 回答
3

根据此处的代码,当需要对默认值进行反序列化时(如果null存在)。

// Omitting other derives, for brevity 
#[derive(Deserialize)]
struct Foo {
   #[serde(deserialize_with = "deserialize_null_default")]
   value: String, 
}

fn deserialize_null_default<'de, D, T>(deserializer: D) -> Result<T, D::Error>
where
    T: Default + Deserialize<'de>,
    D: Deserializer<'de>,
{
    let opt = Option::deserialize(deserializer)?;
    Ok(opt.unwrap_or_default())
}

带有完整示例的操场链接。这也适用于VecHashMap

于 2021-01-13T15:39:48.910 回答