Deserializar una enumeración en Rust con versiones unitarias y no unitarias sin escribir un deserializador personalizado
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 CatVariant
como 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)
Puede hacer esto sin una Deserialize
implementación personalizada, pero será necesario crear algunos tipos adicionales para manejar las diversas alternativas presentes en su esquema.
Como no podemos Animal
implementar directamente Deserialize
su esquema, podemos crear otro tipo que represente ese esquema y luego convertirlo a Animal
. #[serde(from = "OtherType")]
nos permitirá decirle a serde "deserializarlo OtherType
y luego convertirlo a este tipo".
#[derive(Deserialize, Debug)]
#[serde(from = "AnimalRepr")]
enum Animal {
Cat(Option<String>),
Dog(String),
Bird,
}
Entonces, ¿qué AnimalRepr
aspecto 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 AnimalType
manejará los mapas y BareAnimalType
manejará 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 AnimalRepr
a Animal
. Podemos dividir esta conversión teniendo ambos BareAnimalType
y AnimalType
convertibles 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 )