如何区分缺少的反序列化字段和空字段?
How can I distinguish between a deserialized field that is missing and one that is null?
我想使用 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>>,
}
这里不区分b
和c
;两者都是 None
但我希望 b
成为 Some(None)
.
我不受这种嵌套 Option
表示的束缚;任何可以区分这 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)?))
}
然后每个字段将被注释为:
#[serde(deserialize_with = "deserialize_optional_field")]
a: Option<Option<i32>>,
您还需要使用 #[serde(default)]
注释结构,以便将空字段反序列化为 "unwrapped" 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}
在 的基础上,您还可以为三种可能性创建一个枚举:
#[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
对 Patch
的实现将完全处理这个问题,但我还没有想出如何做到这一点。
在 的基础上添加序列化。
use serde::ser::Error;
use serde::{Deserialize, Deserializer};
use serde::{Serialize, Serializer};
// #region ------ JSON Absent support
// build up on top of
/// 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 的 null
和 absent
状态。
更新 2021-03-12 - 更新为 Maybe::Absent
因为它更 JSON 和 SQL DSL 惯用。
这种方法的问题在于我们可以表达:
type | null
默认为 Option<type>
type | null | absent
与 Maybe<type>
但是我们不能表达
type | absent
解决方案是将 Maybe
重构为只有 ::Present(value)
和 ::Absent
并支持 Maybe<Option<type>>
用于 type | null | absent
。所以这将为我们提供全面的报道。
type | null
默认为 Option<type>
type | absent
与 Maybe<type>
type | absent | null
与 Maybe<Option<type>>
我试图在不添加 #[serde(deserialize_with = "deserialize_maybe_field")]
的情况下实现这一点,但不确定是否可行。我可能遗漏了一些明显的东西。
我想使用 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>>,
}
这里不区分b
和c
;两者都是 None
但我希望 b
成为 Some(None)
.
我不受这种嵌套 Option
表示的束缚;任何可以区分这 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)?))
}
然后每个字段将被注释为:
#[serde(deserialize_with = "deserialize_optional_field")]
a: Option<Option<i32>>,
您还需要使用 #[serde(default)]
注释结构,以便将空字段反序列化为 "unwrapped" 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}
在
#[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
对 Patch
的实现将完全处理这个问题,但我还没有想出如何做到这一点。
在
use serde::ser::Error;
use serde::{Deserialize, Deserializer};
use serde::{Serialize, Serializer};
// #region ------ JSON Absent support
// build up on top of
/// 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 的 null
和 absent
状态。
更新 2021-03-12 - 更新为 Maybe::Absent
因为它更 JSON 和 SQL DSL 惯用。
这种方法的问题在于我们可以表达:
type | null
默认为Option<type>
type | null | absent
与Maybe<type>
但是我们不能表达
type | absent
解决方案是将 Maybe
重构为只有 ::Present(value)
和 ::Absent
并支持 Maybe<Option<type>>
用于 type | null | absent
。所以这将为我们提供全面的报道。
type | null
默认为Option<type>
type | absent
与Maybe<type>
type | absent | null
与Maybe<Option<type>>
我试图在不添加 #[serde(deserialize_with = "deserialize_maybe_field")]
的情况下实现这一点,但不确定是否可行。我可能遗漏了一些明显的东西。