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

10

我必须对JSON块进行反序列化,其中一些地方缺少一个完整的对象,编码为具有相同结构但所有字段都设置为默认值(空字符串和零)的对象。

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呢?

2个回答

1
这里有两个需要解决的问题:如果value是所有默认值,则用None替换Some(value);对于Foo,需要处理空字符串情况。
第一个问题很容易解决。对于OptionDeserialize实现会无条件地将它反序列化为Some,除非输入字段是None,因此您需要创建一个自定义的Deserialize实现,在值等于某个标记(如默认值)时,将Some(value)替换为None(这是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 类型都有效。
枚举情况要复杂得多;你面临的问题是 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中,否则会返回反序列化错误。
如果您希望""只有在number0时才是有效的文本,那么您可以做一些更加复杂且可能过度的事情,与使用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个字段),因此除非您具有非常强烈的数据建模要求,否则应该选择向Foo添加 Absent变量。


-3

您可以查看自定义字段反序列化示例

特别是,您可能想要定义类似于以下内容:

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

use serde::{Deserialize, Deserializer, de::Visitor};

fn none_if_all_default<'de, T, D>(deserializer: D) -> Result<Option<T>, D::Error>
where
    T: Deserialize<'de>,
    D: Deserializer<'de> + Clone,
{
    struct AllDefault;

    impl<'de> Visitor<'de> for AllDefault {
        type Value = bool;

        // Implement the visitor functions here -
        // You can recurse over all values to check if they're
        // the empty string or 0, and return true
        //...
    }

    let all_default = deserializer.clone().deserialize_any(AllDefault)?;

    if all_default {
        Ok(None)
    } else {
        Ok(Some(T::deserialize(deserializer)?))
    }
}

然后执行

#[derive(Deserialize)]
struct Outer2 {
    #[serde(deserialize_with = "none_if_all_default")]
    test: Option<Test<Foo>>,
}

3
请完善您的示例,展示如何像 OP 所问的那样对枚举类型进行处理。我尝试了自己的示例,但无法使用枚举类型。由于“not all trait items implemented, missing: `expecting`”,这也无法编译通过。 - Shepmaster
它无法编译,因为它不是用来编译的 - 它只是一个框架答案,实现特性需要手动参考文档完成。重点是展示解决 OP 问题的高级思路。这适用于“enum”字段,因为它适用于任何类型为“Option<T>”的字段。 - Isaac van Bakel
3
“deserialize_any”消耗了“self”。一旦它被消耗掉,如何调用“T::deserialize”?你的函数返回类型不一致(“Result::Ok”和“Option::Some”)。这个答案不正确。 - Shepmaster
4
反序列化器无法克隆。请注意,这种来回操作是为什么强烈建议在回答之前创建一个可行的解决方案的原因。 - Shepmaster

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接