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

Я использую Serde для десериализации пользовательского файла конфигурации, написанного на YAML. Файл может содержать определения различных типов, которые я представляю как перечисления с внутренними тегами:

OfKindFoo:
  kind: Foo
  bar: bar;
  baz: baz;

OfKindQux:
  kind: Qux
  quux: qux;

Я представляю это в Rust так:

#[derive(Deserialize)]
#[serde(tag = "kind")]
enum Definition {
    Foo(Foo),
    Qux(Qux),
}

#[derive(Deserialize)]
struct Foo {
    bar: String,
    baz: String,
}

#[derive(Deserialize)]
struct Qux {
    quux: String,
}

Я хочу, чтобы пользователь мог полностью опустить поле kind, и когда оно опущено, Serde должен по умолчанию десериализовать его как Foo.

Я начал внедрять Deserialize на Definition. Я пытаюсь десериализовать его как карту и искать ключ kind и возвращать соответствующий вариант перечисления на основе этого ключа и того, присутствует ли он.

Мне нужно как-то «переправить» десериализацию других полей карты в Foo::deserialize или Bar::deserialize соответственно. fn deserialize принимает только один аргумент - Deserializer. Есть ли способ «преобразовать» карту в десериализатор или иным образом получить десериализатор, который «запускается» на этой конкретной карте?

Я не могу использовать #[serde(other)], потому что он возвращает Err для отсутствующего тега. Даже если это не так, в документации указано, что other может применяться только к «варианту устройства», варианту, не содержащему никаких данных.


person Kit Isaev    schedule 14.04.2020    source источник


Ответы (1)


Вы можете пометить основное перечисление как untagged и добавить теги к подструктурам, у которых есть тег (эта функция не задокументирован, но был добавлен намеренно и, скорее всего, останется). Вариант без тега должен быть объявлен после других, поскольку serde попытается десериализовать варианты в объявленном порядке с помощью #[serde(untagged)]. Также обратите внимание, что если в вашем фактическом коде варианты и структуры имеют разные имена или вы используете #[serde(rename)], то для (де) сериализации важны имена структур, а не имена вариантов. Все, что применимо к вашему примеру:

#[derive(Deserialize)]
#[serde(untagged)]
enum Definition {
    Qux(Qux),
    Foo(Foo), // variant that doesn't have a tag is the last one
}

#[derive(Deserialize)]
struct Foo {
    bar: String,
    baz: String,
}

#[derive(Deserialize)]
#[serde(tag = "kind")]
// if you want the tag to be "qux" instead of "Qux", do
// #[serde(rename = "qux")]
// here (or add `, rename = "qux"` to the previous serde attribute)
struct Qux {
    quux: String,
}
person jplatte    schedule 15.04.2020