rust 如何区分缺少的反序列化字段和为null的字段?

ldxq2e6h  于 2023-05-22  发布在  其他
关注(0)|答案(4)|浏览(200)

我想使用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不变。
我尝试将每个字段 Package 在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种情况的解决方案都是可以的,例如使用自定义枚举的解决方案。

mzillmmw

mzillmmw1#

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)]注解每个字段(或将其应用于整个结构体)。理想情况下,Deserialize for Patch的实现可以完全处理这个问题,但我还没有弄清楚如何做到这一点。

x6yk4ghg

x6yk4ghg2#

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

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)]注解结构体,以便将空字段反序列化为 “unwrapped”None。技巧是将当前值 Package 在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}
kiz8lqtg

kiz8lqtg3#

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-03-12-更新为Maybe::Absent,因为它更符合JSON和SQL DSL习惯。

这种方法的问题是我们可以表示:

  • type | null与默认Option<type>
  • type | null | absentMaybe<type>

但我们无法表达

  • type | absent

解决方案是将Maybe重构为只有::Present(value)::Absent,并支持type | null | absentMaybe<Option<type>>。这将给予我们全面的覆盖。

  • type | null与默认Option<type>
  • type | absentMaybe<type>
  • type | absent | nullMaybe<Option<type>>

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

eufgjt7s

eufgjt7s4#

您现在可以从serde_with crate中使用double_option,它完全可以满足您的需要。

相关问题