Как настроить реализацию десериализации для определенных типов ввода, но не для всех?

У меня есть такой тип, хотя мой настоящий тип больше и сложнее:

struct MyType {
    i: u32,
}

Если я реализую Deserialize для этого типа serde ищет что-то вроде этого (меня интересует JSON):

{"i":100}  

Я хочу настроить его так, чтобы можно было десериализовать и из байтового массива:

[1, 2, 3, 4]

Я могу написать impl для обработки массива, но я хочу, чтобы serde автоматически генерировал остальное (что будет visit_map):

impl<'de> Deserialize<'de> for MyType {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        struct MyTypeVisitor;

        impl<'de> Visitor<'de> for MyTypeVisitor {
            type Value = MyType;

            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                write!(formatter, "struct or array of 4 integers")
            }

            fn visit_seq<A: SeqAccess<'de>>(self, seq: A) -> Result<Self::Value, A::Error> {
                // ...
            }
        }

        // deserializer.deserialize_any(MyTypeVisitor)
    }
}

Это возможно? В этом примере это не сложно, но когда структура большая, десериализация может быть болезненной.

Это не дубликат Как преобразовать поля при десериализации с использованием Serde? так как deserialize_with работает только на 1 поле. Я не могу понять, как я мог бы заставить это работать для моего реального типа:

pub enum Component {
    String(StringComponent),
    Translation(TranslationComponent),
    Score(ScoreComponent),
    Selector(SelectorComponent),
}

pub struct StringComponent {
    #[serde(flatten)] pub base: Base,
    pub text: String,
}

pub struct Base {
    // ...
    extra: Option<Vec<Component>>,
    // ...
}

Что я хочу сделать, это:

  • При десериализации, если вход является числом, вернуть Component::String, Это может быть сделано с visit_i/u/f64 и друзья.
  • Если вход является строкой, вернуть Component::String снова. Это может быть сделано с visit_str/string,
  • Если вход является массивом [..], десериализовать его как обычно, но сделать присвоение элементов в массиве [1..] дополнительному массиву [0]. Это может быть сделано visit_seq,
  • Если вход является картой, пусть serde производный обрабатывает его.

1 ответ

Решение

В документации Serde есть пример, показывающий, как реализовать десериализацию из строки или структуры. Это эквивалентно вашему случаю, только меньше.

Важная часть заключается в следующем:

fn visit_map<M>(self, visitor: M) -> Result<T, M::Error>
where
    M: MapAccess<'de>,
{
    Deserialize::deserialize(de::value::MapAccessDeserializer::new(visitor))
}

Это делегирует встроенную реализацию десериализации. Так как все остальные ваши дела являются заказными, это должно подойти.

Другие вопросы по тегам