json - 如何将所有字段都是默认值的类型反序列化为 None ?

标签 json rust serde serde-json

我必须反序列化 JSON blob,在某些地方,整个对象的缺失被编码为具有相同结构但其所有字段都设置为默认值(空字符串和零)的对象。

extern crate serde_json; // 1.0.27
#[macro_use] extern crate serde_derive; // 1.0.78
extern crate serde; // 1.0.78

#[derive(Debug, Deserialize)]
struct Test<T> {
    text: T,
    number: i32,
}

#[derive(Debug, Deserialize)]
struct Outer {
    test: Option<Test<String>>,
}

#[derive(Debug, Deserialize)]
enum Foo { Bar, Baz }
#[derive(Debug, Deserialize)]
struct Outer2 {
    test: Option<Test<Foo>>,
}

fn main() {
    println!("{:?}", serde_json::from_str::<Outer>(r#"{ "test": { "text": "abc", "number": 42 } }"#).unwrap());
    // good: Outer { test: Some(Test { text: "abc", number: 42 }) }

    println!("{:?}", serde_json::from_str::<Outer>(r#"{ "test": null }"#).unwrap());
    // good: Outer { test: None }

    println!("{:?}", serde_json::from_str::<Outer>(r#"{ "test": { "text": "", "number": 0 } }"#).unwrap());
    // bad: Outer { test: Some(Test { text: "", number: 0 }) }
    // should be: Outer { test: None }

    println!("{:?}", serde_json::from_str::<Outer2>(r#"{ "test": { "text": "Bar", "number": 42 } }"#).unwrap());
    // good: Outer2 { test: Some(Test { text: Bar, number: 42 }) }

    println!("{:?}", serde_json::from_str::<Outer2>(r#"{ "test": { "text": "", "number": 0 } }"#).unwrap());
    // bad: error
    // should be: Outer { test: None }
}

我会在反序列化后处理这个问题,但正如您所见,这种方法不适用于枚举值:没有变体与空字符串匹配,因此反序列化完全失败。

我如何教它进行 serde?

最佳答案

这里有两件事需要解决:替换Some(value)None如果value都是默认值,并处理 Foo 的空字符串大小写.

第一件事很简单。 Deserialize Option 的实现无条件反序列化为 Some如果输入字段不是 None , 所以你需要创建一个自定义 Deserialize替换 Some(value) 的实现与 None如果value等于一些哨兵,比如默认值(这是 Issac 提出的答案,但在这里正确实现):

fn none_if_all_default<'de, T, D>(deserializer: D) -> Result<Option<T>, D::Error>
where
    T: Deserialize<'de> + Default + Eq,
    D: Deserializer<'de>,
{
    Option::deserialize(deserializer).map(|opt| match opt {
        Some(value) if value == T::default() => None,
        opt => opt,
    })
}

#[derive(Deserialize)]
struct Outer<T: Eq + Default> {
    #[serde(deserialize_with = "none_if_all_default")]
    #[serde(bound(deserialize = "T: Deserialize<'de>"))]
    test: Option<Test<T>>,
}

这解决了你问题的前半部分,用 Option<Test<String>> .这适用于任何可反序列化的类型 Eq + Default .

enum案例要棘手得多;你面临的问题是 Foo根本不会从 "Bar" 以外的字符串反序列化或 "Baz" .除了向枚举添加第三个“死”变体之外,我真的没有看到一个好的解决方案:

#[derive(PartialEq, Eq, Deserialize)]
enum Foo {
    Bar,
    Baz,

    #[serde(rename = "")]
    Absent,
}

impl Default for Foo { fn default() -> Self { Self::Absent } }

从数据建模的角度来看,存在这个问题的原因是它必须考虑到您会得到这样的 json 的可能性:

{ "test": { "text": "", "number": 42 } }

在这种情况下,显然 Outer { test: None } 不是正确的结果,但它仍然需要一个值来存储在 Foo 中, 否则返回反序列化错误。

如果你希望它成为 "" 的情况如果number 是有效文本0 ,与仅使用 Absent 相比,您可以显着做一些更复杂的事情,并且可能会过度满足您的需求。 .您需要使用未标记的枚举,它可以存储“有效” Test或“全空”Test ,然后创建一个反序列化默认值的结构版本:

struct MustBeDefault<T> {
    marker: PhantomData<T>
}

impl<'de, T> Deserialize<'de> for MustBeDefault<T>
where
    T: Deserialize<'de> + Eq + Default
{
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>
    {
        match T::deserialize(deserializer)? == T::default() {
            true => Ok(MustBeDefault { marker: PhantomData }),
            false => Err(D::Error::custom("value must be default"))
        }
    }
}

// All fields need to be generic in order to use this solution.
// Like I said, this is radically overkill.
#[derive(Deserialize)]
struct Test<T, U> {
    text: T,
    number: U,
}

#[derive(Deserialize)]
#[serde(untagged)]
enum MaybeDefaultedTest<T> {
    AllDefault(Test<EmptyString, MustBeDefault<i32>>),
    Normal(Test<Foo, i32>),
}

// `EmptyString` is a type that only deserializes from empty strings;
// its implementation is left as an exercise to the reader.
// You'll also need to convert from MaybeDefaultedTest<T> to Option<T>;
// this is also left as an exercise to the reader.

现在可以写MaybeDefaulted<Foo> ,它将反序列化自 {"text": "", "number": 0} 之类的东西或 {"text": "Baz", "number": 10}{"text": "Baz", "number": 0} , 但无法从 {"text": "", "number": 10} 反序列化.

再一次,第三次,这个解决方案可能完全矫枉过正(特别是如果您的实际用例涉及 Test 结构中的 2 个以上字段),因此除非您有非常强烈的数据建模要求,否则您应该添加一个 Absent Foo 的变体.

关于json - 如何将所有字段都是默认值的类型反序列化为 None ?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52611244/

相关文章:

java - 如何在 Spring boot Api post 方法中接收自定义的 JSON 对象

javascript - 访问 JSON 响应中的字段

rust - 在 Rust 中生成进程时重定向 stdio 的正确方法是什么?

rust - 使用serde_cbor在Rust中将Vec <u8>序列化为CBOR字节字符串

rust - 如何使用 Serde 序列化具有顶级键的结构?

rust - 实现一个复杂的自定义结构序列化器

java - 某些字段的 jackson JSON 自定义序列化

rust - 别名可变原始指针 (*mut T) 会导致未定义的行为吗?

rust - BinaryHeap 是仅将 PartialEq 用于排序顺序还是用于真正的等价?

jquery - 如何使用本地 JSON 对象作为 jQuery DataTables 的数据源