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

She*_*ter 11 rust serde

我想使用Serde来解析一些JSON作为HTTP PATCH请求的一部分.由于PATCH请求不会传递整个对象,只有要更新的相关数据,我需要能够在未传递的值,显式设置null的值和存在的值之间进行分析.

我有一个具有多个可空字段的值对象:

struct Resource {
    a: Option<i32>,
    b: Option<i32>,
    c: Option<i32>,
}
Run Code Online (Sandbox Code Playgroud)

如果客户端提交JSON,如下所示:

{"a": 42, "b": null}
Run Code Online (Sandbox Code Playgroud)

我想换aSome(42),bNone,并留下c不变.

我尝试将每个字段包装在另外一个级别Option:

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

操场

这并没有区分bc; 两者都是,None但我一直想b成为Some(None).

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

E_n*_*ate 7

很可能,现在实现这一目标的唯一方法是使用自定义反序列化功能.幸运的是,它并不难实现,即使它适用于任何类型的领域:

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)?))
}
Run Code Online (Sandbox Code Playgroud)

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

#[serde(deserialize_with = "deserialize_optional_field")]
a: Option<Option<i32>>,
Run Code Online (Sandbox Code Playgroud)

您还需要使用注释结构#[serde(default)],以便将空字段反序列化为"unwrapped" None.诀窍是包装现有值Some.

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

#[serde(deserialize_with = "deserialize_optional_field")]
#[serde(skip_serializing_if = "Option::is_none")]
a: Option<Option<i32>>,
Run Code Online (Sandbox Code Playgroud)

游乐场有完整的例子.输出:

Original JSON: {"a": 42, "b": null}
> Resource { a: Some(Some(42)), b: Some(None), c: None }
< {"a":42,"b":null}
Run Code Online (Sandbox Code Playgroud)


She*_*ter 7

基于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)
    }
}
Run Code Online (Sandbox Code Playgroud)

然后可以用作:

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

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

  • “理想情况下,‘Patch’的‘Deserialize’的实现将完全处理这个问题”到目前为止我的假设是这是不可能的。从您尝试反序列化“Patch”的那一刻起,您就期望该值以其序列化形式存在。相反,`Patch::Missing` 通过其在我们的容器中的“不存在”而存在(您实际上无法自行将 `Patch::Missing` 序列化为 JSON)。AFAIK“序列化”不能选择不被序列化,也不能告诉容器跳过该过程的这一部分。 (3认同)