【问题标题】:How to deserialize a JSON file which contains null values using Serde?如何使用 Serde 反序列化包含空值的 JSON 文件?
【发布时间】:2017-05-26 15:41:23
【问题描述】:

我想使用 Serde 反序列化来自 Bowserinator on github 的化学元素 JSON 文件。为此,我创建了一个包含所有必需字段的结构并派生了所需的宏:

#[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,
}

这工作正常,直到它到达包含“空”值的字段。 例如。用于 Helium 中的字段 "color": null,

我收到的错误消息是 { code: Message("invalid type: unit value, expected a string"), line: 8, column: 17 } 用于该字段。

我尝试了#[serde(default)] 宏。但这仅在 JSON 文件中缺少该字段时有效,而不是在存在 null 值时有效。

我喜欢使用标准宏进行反序列化,避免对访客特征进行编程。有没有我想念的把戏?

【问题讨论】:

  • 强烈建议阅读The Rust Programming Language,其中涵盖了OptionResult 的概念,它们在Rust 中非常普遍。
  • 我已经这样做了,但是提示如何处理这种情况会有所帮助,因为我似乎需要考虑与我预期的有点不同。正如我上面所说,我的假设是我需要实现访客特征,我想避免这种情况。正如我在下面所说的:我也想避免第二次解析所有读取的结构,并希望 Serde 有一些魔法可以帮助。
  • 如果您提供minimal reproducible example,您的问题会更清楚。照原样,您提供了代码和输入,但没有提供您想要的输出。如您所见,您提出的模棱两可导致了两个截然不同的答案。
  • 好的,谢谢,下次我会这样做。

标签: json rust serde


【解决方案1】:

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

解决这个问题的一种方法是调整我们应用程序的规范以接受@user25064 的回答所指定的null | string

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

Playground with minimal example

另一种方法是为字段编写我们自己的反序列化例程,它将接受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())
        })
}

Playground

另见:

【讨论】:

  • 谢谢你,特别是你的解释。我想我会选择第二种方式,这样我就可以避免翻译类(从带有 Option 的结构到我喜欢使用的结构)
  • 我真的希望 Serde 能以更好的方式处理这个问题。 null 在 Rust 中不是有效值,但 在 JSON 中的有效值,因此 Serde 应该只实现基本的 JSON 标准。目前的解决方案都是冗长且次优的。对每个字段使用 Option<...>(然后对每个字段使用 unwrap_or_else,呵呵...),或者在每个字段上附加一个 #[serde(deserialize_with="...")],这实际上似乎可能更好
【解决方案2】:

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

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

【讨论】:

  • 我希望有一些技巧可以自动进行这种转换。我想避免在它从 Serde 解析器返回后再次解析 Element 结构并自己修复所有空值。
【解决方案3】:

基于来自here 的代码,当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())
}

playground link 完整示例。这也适用于VecHashMap

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-20
    • 2015-05-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多