Deserializar una enumeración en Rust con versiones unitarias y no unitarias sin escribir un deserializador personalizado

Resuelto Ian Joiner asked hace 9 meses • 1 respuestas
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Debug)]
enum Animal {
    Cat(Option<String>),
    Dog(String),
    Bird,
}

fn main() {
    let json_animals = r#"
        [
            {"Cat": "Meow"},
            "Cat",
            {"Dog": "Bark"},
            "Bird"
        ]"#;
    println!("{:?}", serde_json::from_str::<Vec<Animal>>(json_animals).unwrap());
}

Básicamente necesitamos una manera de deserializar ambos "Cat"y {"Cat": "cat_name"}en Animal. Sé que escribir un deserializador personalizado funciona, pero será mejor tener una solución más limpia.

Intenté crear un alias, cambiar el nombre y las opciones y definirlo CatVariantcomo una enumeración separada para incluir ambos casos. Ninguno de estos funciona debido aError("invalid type: unit variant, expected newtype variant", line: 5, column: 12)

Ian Joiner avatar Feb 16 '24 03:02 Ian Joiner
Aceptado

Puede hacer esto sin una Deserializeimplementación personalizada, pero será necesario crear algunos tipos adicionales para manejar las diversas alternativas presentes en su esquema.

Como no podemos Animalimplementar directamente Deserializesu esquema, podemos crear otro tipo que represente ese esquema y luego convertirlo a Animal. #[serde(from = "OtherType")]nos permitirá decirle a serde "deserializarlo OtherTypey luego convertirlo a este tipo".

#[derive(Deserialize, Debug)]
#[serde(from = "AnimalRepr")]
enum Animal {
    Cat(Option<String>),
    Dog(String),
    Bird,
}

Entonces, ¿qué AnimalRepraspecto tiene? Bueno, es un mapa o una cadena. Podemos usar una enumeración sin etiquetar para representar eso.

#[derive(Deserialize, Debug)]
#[serde(untagged)]
enum AnimalRepr {
    Animal(AnimalType),
    BareAnimalType(BareAnimalType),
}

Así que ahora AnimalTypemanejará los mapas y BareAnimalTypemanejará las cadenas.

#[derive(Deserialize, Debug)]
enum BareAnimalType {
    Cat,
    Bird,
}

#[derive(Deserialize, Debug)]
enum AnimalType {
    Cat(String),
    Dog(String),
}

Ahora sólo necesitamos una forma de convertir AnimalRepra Animal. Podemos dividir esta conversión teniendo ambos BareAnimalTypey AnimalTypeconvertibles en Animal, y delegando a esa conversión al convertir AnimalRepr.

impl From<BareAnimalType> for Animal {
    fn from(value: BareAnimalType) -> Self {
        match value {
            BareAnimalType::Cat => Self::Cat(None),
            BareAnimalType::Bird => Self::Bird,
        }
    }
}

impl From<AnimalType> for Animal {
    fn from(value: AnimalType) -> Self {
        match value {
            AnimalType::Cat(n) => Self::Cat(Some(n)),
            AnimalType::Dog(n) => Self::Dog(n),
        }
    }
}

impl From<AnimalRepr> for Animal {
    fn from(value: AnimalRepr) -> Self {
        match value {
            AnimalRepr::Animal(v) => v.into(),
            AnimalRepr::BareAnimalType(v) => v.into(),
        }
    }
}

Sumando todo tenemos:

use serde::{Deserialize, Serialize};

#[derive(Deserialize, Debug)]
enum BareAnimalType {
    Cat,
    Bird,
}

impl From<BareAnimalType> for Animal {
    fn from(value: BareAnimalType) -> Self {
        match value {
            BareAnimalType::Cat => Self::Cat(None),
            BareAnimalType::Bird => Self::Bird,
        }
    }
}

#[derive(Deserialize, Debug)]
enum AnimalType {
    Cat(String),
    Dog(String),
}

impl From<AnimalType> for Animal {
    fn from(value: AnimalType) -> Self {
        match value {
            AnimalType::Cat(n) => Self::Cat(Some(n)),
            AnimalType::Dog(n) => Self::Dog(n),
        }
    }
}

#[derive(Deserialize, Debug)]
#[serde(untagged)]
enum AnimalRepr {
    Animal(AnimalType),
    BareAnimalType(BareAnimalType),
}

impl From<AnimalRepr> for Animal {
    fn from(value: AnimalRepr) -> Self {
        match value {
            AnimalRepr::Animal(v) => v.into(),
            AnimalRepr::BareAnimalType(v) => v.into(),
        }
    }
}

#[derive(Deserialize, Debug)]
#[serde(from = "AnimalRepr")]
enum Animal {
    Cat(Option<String>),
    Dog(String),
    Bird,
}

fn main() {
    let json_animals = r#"
        [
            {"Cat": "Meow"},
            "Cat",
            {"Dog": "Bark"},
            "Bird"
        ]"#;
    println!("{:?}", serde_json::from_str::<Vec<Animal>>(json_animals).unwrap());
}

Qué salidas:

[Cat(Some("Meow")), Cat(None), Dog("Bark"), Bird]

( Patio de juegos )

cdhowie avatar Feb 15 '2024 21:02 cdhowie