如何区分反序列化后缺失的字段和空字段?

24

我希望使用Serde来解析HTTP PATCH请求中的一些JSON数据。由于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,但我希望bSome(None)

我不固定于这种嵌套Option的表示方式;任何能够区分这3种情况的解决方案都可以,比如使用自定义枚举的解决方案。

4个回答

16

基于 E_net4 的回答,你也可以创建一个枚举类型来表示这三种可能性:

#[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)]为每个字段进行注释(或将其应用于整个结构体)。理想情况下,PatchDeserialize实现应该完全处理这一点,但我还没有弄清楚如何做到这一点。


3
理想情况下,对于 PatchDeserialize 实现将完全处理这一点。到目前为止,我的假设是这是不可能的。从你尝试反序列化 Patch 开始,你期望该值以其序列化形式存在。相反,Patch::Missing 通过在容器中的“不存在”而存在(你不能将 Patch::Missing 单独序列化为 JSON)。据我所知,Serialize 无法选择不被序列化,也无法告诉容器跳过该过程的那一部分。 - E net4
1
实际上,我的先前评论更适用于序列化,而不是反序列化。无论如何,逻辑有些双重:Deserialize 无法告诉 Deserializer 反序列化它根本找不到的值。如果我们有一个空对象 {},那么 Deserialize 的实现将对此无能为力,但是一旦反序列化器知道要填充默认值,它就可以做到。 - E net4
我更喜欢这种方法而不是“双重选项”模式,因为它给出了比包装选项更明确和易懂的类型-谢谢!对我来说解决了https://stackoverflow.com/q/56661241/141881的问题:D - pospi

15

目前很可能唯一实现这一点的方法是使用自定义反序列化函数。幸运的是,即使是让它适用于任何类型的字段,也不难实现:

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根据请求返回不同的空值或无值项。经过很多折腾,我基于上述代码实现了自己的函数,但这不应该这么难吧? - Rob Mascaro

2

Shepmaster的回答基础上,加入序列化。

use serde::ser::Error;
use serde::{Deserialize, Deserializer};
use serde::{Serialize, Serializer};

// #region ------ JSON Absent support
// build up on top of https://dev59.com/_VcP5IYBdhLWcg3wf54G#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-03-12 - 更新为Maybe::Absent,因为它更符合JSON和SQL DSL的习惯用法。
这种方法的问题在于我们可以使用以下方式表示:
- 用默认值Option<type>表示type | null - 使用Maybe<type>表示type | null | absent 但是我们无法表示type | absent
解决方案是将Maybe重构为只具有::Present(value)::Absent,并支持Maybe<Option<type>>用于type | null | absent。这样就可以实现全覆盖。
- 用默认值Option<type>表示type | null - 使用Maybe<type>表示type | absent - 使用Maybe<Option<type>>表示type | absent | null 我正在尝试在不添加#[serde(deserialize_with = "deserialize_maybe_field")]的情况下实现此目标,但不确定是否可能。我可能忽略了一些显而易见的东西。

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接