2017-05-26 5 views
2

Je veux désérialiser le fichier JSON des éléments chimiques de Bowserinator on github en utilisant Serde. Pour cela, je créé une structure avec tous les champs nécessaires et tirais les macros nécessaires:Comment désérialiser un fichier JSON contenant des valeurs nulles en utilisant Serde?

#[derive(Serialize, Deserialize, Debug, Clone)] 
pub struct Element { 
    name: String, 
    appearance: String, 
    atomic_mass: f64, 
    boil: f64, 
    category: String, 
    #[serde(default)] 
    color: String, 
    density: f64, 
    discovered_by: String, 
    melt: f64, 
    #[serde(default)] 
    molar_heat: f64, 
    named_by: String, 
    number: String, 
    period: u32, 
    phase: String, 
    source: String, 
    spectral_img: String, 
    summary: String, 
    symbol: String, 
    xpos: u32, 
    ypos: u32, 
} 

Cela fonctionne bien jusqu'à ce qu'il soit à des champs qui contiennent une valeur « null ». E.g. pour le champ "color": null, en hélium.

Le message d'erreur que j'obtiens est { code: Message("invalid type: unit value, expected a string"), line: 8, column: 17 } pour ce champ. J'ai expérimenté la macro #[serde(default)]. Mais cela ne fonctionne que lorsque le champ est manquant dans le fichier JSON, et non lorsqu'il y a une valeur null. J'aime faire la désérialisation avec les macros standard en évitant de programmer un trait de visiteur. Y a-t-il un truc qui me manque?

+1

Il est fortement recommandé ** ** que vous avez lu (https://doc.rust-lang.org/stable/book/) [* La programmation Rust Langue *], qui couvre le concept de 'Option' et' Result', qui sont très répandus dans Rust. – Shepmaster

+0

Je l'ai déjà fait, mais un indice serait utile pour gérer ce cas, car il semble que je doive penser un peu différent de ce à quoi je m'attendais. Comme je l'ai dit ci-dessus, mon hypothèse est que j'ai besoin de mettre en œuvre le trait de visiteur et je voulais éviter cela. Comme je l'ai dit ci-dessous: Je voulais aussi éviter d'analyser toutes les Structures lues une seconde fois et j'espérais que Serde ait une sorte de magie pour aider. – Hartmut

+0

Votre question serait plus claire si vous fournissiez un [MCVE]. En l'état, vous avez fourni le code et l'entrée, mais pas ce que vous voulez *. Comme vous pouvez le voir, l'ambiguïté que vous avez présentée a donné lieu à deux réponses radicalement différentes. – Shepmaster

Répondre

6

Une erreur de désérialisation se produit parce que la définition de struct est incompatible avec les objets entrants: le champ color peut aussi être null, ainsi qu'une chaîne, mais donnant ce champ les forces de type String votre programme pour attendre toujours chaîne. C'est le comportement par défaut, ce qui est logique. Sachez que String (ou d'autres conteneurs tels que Box) ne sont pas "nulles" dans Rust. Comme pour une valeur null ne déclenchant pas la valeur par défaut à la place, c'est ainsi que fonctionne Serde: si le champ d'objet n'était pas là, cela fonctionnerait parce que vous avez ajouté l'attribut de champ par défaut. D'autre part, un champ "couleur" avec la valeur null n'est pas équivalent à aucun champ du tout.

Une façon de résoudre ce problème est d'ajuster la spécification de notre application à accepter null | string, tel que spécifié par @ réponse de user25064:

#[derive(Serialize, Deserialize, Debug, Clone)] 
pub struct Element { 
    color: Option<String>, 
} 

Playground with minimal example

Une autre façon est d'écrire notre propre routine désérialisation pour le champ , qui acceptera null et tourner à autre chose de type String. Cela peut être fait avec l'attribut #[serde(deserialize_with=...)].

#[derive(Serialize, Deserialize, Debug, Clone)] 
pub struct Element { 
    #[serde(deserialize_with="parse_color")] 
    color: String, 
} 

fn parse_color<'de, D>(d: D) -> Result<String, D::Error> where D: Deserializer<'de> { 
    Deserialize::deserialize(d) 
     .map(|x: Option<_>| { 
      x.unwrap_or("black".to_string()) 
     }) 
} 

Playground

+0

Merci, surtout pour l'explication. Je pense que je vais aller avec la deuxième façon, donc je peux éviter une classe de traduction (d'une structure avec l'option à la structure avec laquelle j'aime travailler) – Hartmut

2

Tout champ pouvant être nul doit être de type Option afin que vous puissiez gérer le cas nul. Quelque chose comme ça?

#[derive(Serialize, Deserialize, Debug, Clone)] 
pub struct Element { 
    ... 
    color: Option<String>, 
    ... 
} 
+0

J'espérais qu'il y avait un truc pour automatiser cette conversion. Je voulais éviter d'analyser la structure de l'élément une seconde fois après son retour de l'analyseur Serde et réparer moi-même toutes les valeurs nulles. – Hartmut

+2

Peut-être que vous recherchez [défaut serde] (https://serde.rs/attr-default.html) – user25064