Как я могу десериализовать тип, где все поля являются значениями по умолчанию как None?

Я должен десериализовать BLOB-объекты 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 }
}

Я бы обработал это после десериализации, но, как вы можете видеть, этот подход невозможен для значений enum: ни один вариант не соответствует пустой строке, поэтому десериализация завершается неудачей полностью.

Как я могу научить это serde?

2 ответа

Здесь нужно решить две вещи: заменить на if is all defaults и обработать случай пустой строки для .

Первое легко. Реализация для Optionбезоговорочно десериализует его как Someесли поле ввода не , поэтому вам нужно создать пользовательский Deserializeреализация, заменяющая Some(value)с Noneесли valueравен некоторому дозорному, например, по умолчанию (это ответ, предложенный Исааком, но реализованный здесь правильно):

      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.

The enumслучай намного сложнее; проблема, с которой вы столкнулись, заключается в том, что просто не будет десериализоваться из строки, отличной от "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 }не является правильным результатом, но ему все равно нужно сохранить значение в , иначе он вернет ошибку десериализации.

Если вы хотите, чтобы это было так, ""допустимый текст , только если numberявляется 0, вы могли бы сделать что-то значительно более сложное и, вероятно, излишнее для ваших нужд, по сравнению с простым использованием . Вам нужно будет использовать перечисление без тегов, которое может хранить либо "valid", либо "all empty", а затем создать версию вашей структуры, которая только десериализует значения по умолчанию:

      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}.

Опять же, в третий раз, это решение, вероятно, радикально излишне (особенно если ваш реальный вариант использования включает более 2 полей в Teststruct), и поэтому, если у вас нет очень строгих требований к моделированию данных, вам следует добавить Absentвариант Foo.

Вы можете посмотреть пример десериализации пользовательских полей.

В частности, вы можете определить что-то вроде

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>>,
}
Другие вопросы по тегам