【问题标题】:How can I distinguish between a deserialized field that is missing and one that is null?如何区分缺失的反序列化字段和空字段?
【发布时间】:2017-06-02 14:12:29
【问题描述】:

我想使用 Serde 来解析一些 JSON 作为 HTTP PATCH 请求的一部分。由于 PATCH 请求不传递整个对象,仅传递要更新的相关数据,因此我需要能够区分未传递的值、显式设置为 null 的值和存在的值。

我有一个包含多个可为空字段的值对象:

struct Resource {
    a: Option<i32>,
    b: Option<i32>,
    c: Option<i32>,
}

如果客户端这样提交 JSON:

{"a": 42, "b": null}

我想将a 更改为Some(42),将b 更改为None,并保持c 不变。

我尝试将每个字段包装在Option 的另一层中:

#[derive(Debug, Deserialize)]
struct ResourcePatch {
    a: Option<Option<i32>>,
    b: Option<Option<i32>>,
    c: Option<Option<i32>>,
}

playground

这并没有区分bc;两者都是None,但我希望b 成为Some(None)

我不依赖嵌套Options 的这种表示;任何可以区分这 3 种情况的解决方案都可以,例如使用自定义枚举的解决方案。

【问题讨论】:

    标签: rust serde


    【解决方案1】:

    E_net4's answer 的基础上,您还可以为三种可能性创建一个枚举:

    #[derive(Debug)]
    enum Patch<T> {
        Missing,
        Null,
        Value(T),
    }
    
    impl<T> Default for Patch<T> {
        fn default() -> Self {
            Patch::Missing
        }
    }
    
    impl<T> From<Option<T>> for Patch<T> {
        fn from(opt: Option<T>) -> Patch<T> {
            match opt {
                Some(v) => Patch::Value(v),
                None => Patch::Null,
            }
        }
    }
    
    impl<'de, T> Deserialize<'de> for Patch<T>
    where
        T: Deserialize<'de>,
    {
        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
        where
            D: Deserializer<'de>,
        {
            Option::deserialize(deserializer).map(Into::into)
        }
    }
    

    这可以用作:

    #[derive(Debug, Deserialize)]
    struct ResourcePatch {
        #[serde(default)]
        a: Patch<i32>,
    }
    

    不幸的是,您仍然必须使用 #[serde(default)] 注释每个字段(或将其应用于整个结构)。理想情况下,为Patch 实现Deserialize 可以完全解决这个问题,但我还没有弄清楚如何做到这一点。

    【讨论】:

    • “理想情况下,为Patch 实现Deserialize 将完全处理这个问题”到目前为止,我的假设是这是不可能的。从您尝试反序列化Patch 的那一刻起,您就希望该值以其序列化形式存在。相反,Patch::Missing 通过它在我们容器中的“不存在”而存在(您实际上无法将Patch::Missing 自己序列化为 JSON)。 AFAIK Serialize 不能选择不被序列化,也不能告诉容器跳过该部分的过程。
    • 嗯,其实我之前的评论更适用于序列化,而不是反序列化。无论如何,逻辑有点双重:Deserialize 不能告诉Deserializer 反序列化它找不到的值。如果我们有一个空对象{}Deserialize 的实现对此无能为力,但反序列化器可以,一旦它知道填写默认值。
    • 我更喜欢这种“双重选项”模式的方法,因为它为您提供比包装选项更明确和易于理解的类型 - 谢谢!为我解决了stackoverflow.com/q/56661241/141881:D
    【解决方案2】:

    很有可能,目前实现这一目标的唯一方法是使用自定义反序列化函数。幸运的是,它并不难实现,甚至适用于任何类型的领域:

    fn deserialize_optional_field<'de, T, D>(deserializer: D) -> Result<Option<Option<T>>, D::Error>
    where
        D: Deserializer<'de>,
        T: Deserialize<'de>,
    {
        Ok(Some(Option::deserialize(deserializer)?))
    }
    

    然后每个字段将被注释为:

    #[serde(deserialize_with = "deserialize_optional_field")]
    a: Option<Option<i32>>,
    

    您还需要使用#[serde(default)] 注释结构,以便将空字段反序列化为“未包装” None。诀窍是将当前值包裹在 Some 周围。

    序列化依赖另一个技巧:当字段为None时跳过序列化:

    #[serde(deserialize_with = "deserialize_optional_field")]
    #[serde(skip_serializing_if = "Option::is_none")]
    a: Option<Option<i32>>,
    

    Playground 带有完整示例。输出:

    Original JSON: {"a": 42, "b": null}
    > Resource { a: Some(Some(42)), b: Some(None), c: None }
    < {"a":42,"b":null}
    

    【讨论】:

    • 我有点吃惊,直到今天我们还没有一个简单的指令来关闭结构项目或结构内结构的反序列化。上面的代码是一个很好的开始,但不适用于返回空的结构中的结构。许多 Rest API 有不同的项,根据请求返回 null 或 none。说了很多废话,我已经根据上述实现了我自己的功能,但它不应该这么难吗?
    【解决方案3】:

    Shepmaster's answer 为基础并添加序列化。

    use serde::ser::Error;
    use serde::{Deserialize, Deserializer};
    use serde::{Serialize, Serializer};
    
    // #region ------ JSON Absent support
    // build up on top of https://stackoverflow.com/a/44332837
    
    /// serde Valueue that can be Absent, Null, or Valueue(T)
    #[derive(Debug)]
    pub enum Maybe<T> {
        Absent,
        Null,
        Value(T),
    }
    
    #[allow(dead_code)]
    impl<T> Maybe<T> {
        pub fn is_absent(&self) -> bool {
            match &self {
                Maybe::Absent => true,
                _ => false,
            }
        }
    }
    
    impl<T> Default for Maybe<T> {
        fn default() -> Self {
            Maybe::Absent
        }
    }
    
    impl<T> From<Option<T>> for Maybe<T> {
        fn from(opt: Option<T>) -> Maybe<T> {
            match opt {
                Some(v) => Maybe::Value(v),
                None => Maybe::Null,
            }
        }
    }
    
    impl<'de, T> Deserialize<'de> for Maybe<T>
    where
        T: Deserialize<'de>,
    {
        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
        where
            D: Deserializer<'de>,
        {
            let d = Option::deserialize(deserializer).map(Into::into);
            d
        }
    }
    
    impl<T: Serialize> Serialize for Maybe<T> {
        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
        where
            S: Serializer,
        {
            match self {
                // this will be serialized as null
                Maybe::Null => serializer.serialize_none(),
                Maybe::Value(v) => v.serialize(serializer),
                // should have been skipped
                Maybe::Absent => Err(Error::custom(
                    r#"Maybe fields need to be annotated with: 
      #[serde(default, skip_serializing_if = "Maybe::is_Absent")]"#,
                )),
            }
        }
    }
    // #endregion --- JSON Absent support
    

    然后你可以这样使用它:

    #[derive(Serialize, Deserialize, Debug)]
    struct Rect {
        #[serde(default, skip_serializing_if = "Maybe::is_absent")]
        stroke: Maybe<i32>,
    
        w: i32,
    
        #[serde(default, skip_serializing_if = "Maybe::is_absent")]
        h: Maybe<i32>,
    }
    
    
    // .... 
    
    
    let json = r#"
    {
      "stroke": null,
      "w": 1
    }"#;
        
    let deserialized: Rect = serde_json::from_str(json).unwrap();
    println!("deserialized = {:?}", deserialized);
    // will output: Rect { stroke: Null, w: 1, h: Absent }
    
    let serialized = serde_json::to_string(&deserialized).unwrap();
    println!("serialized back = {}", serialized);
    // will output: {"stroke":null,"w":1}
    

    我希望 Serde 有一种内置方式来处理 JSON 的 nullabsent 状态。

    2021 年 3 月 12 日更新 - 更新为 Maybe::Absent,因为它更符合 JSON 和 SQL DSL 习惯。

    这种方法的关键在于我们可以表达:

    • type | null 与默认 Option&lt;type&gt;
    • type | null | absentMaybe&lt;type&gt;

    但我们无法表达

    • type | absent

    解决方案是将Maybe 重构为只有::Present(value)::Absent 并支持Maybe&lt;Option&lt;type&gt;&gt;type | null | absent。所以这将为我们提供全面的覆盖。

    • type | null 与默认 Option&lt;type&gt;
    • type | absentMaybe&lt;type&gt;
    • type | absent | nullMaybe&lt;Option&lt;type&gt;&gt;

    我试图在不添加#[serde(deserialize_with = "deserialize_maybe_field")] 的情况下实现这一点,但不确定是否可行。我可能遗漏了一些明显的东西。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-08-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多