我想使用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)
我想换a到Some(42),b对None,并留下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)
这并没有区分b和c; 两者都是,None但我一直想b成为Some(None).
我不依赖于嵌套Options的这种表示; 任何可以区分3个案例的解决方案都可以,例如使用自定义枚举的解决方案.
很可能,现在实现这一目标的唯一方法是使用自定义反序列化功能.幸运的是,它并不难实现,即使它适用于任何类型的领域:
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)
基于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可以完全解决该问题,但是我还没有弄清楚该怎么做。