I want to deserialize the chemical elements JSON file from Bowserinator on github using Serde. For this I created a structure with all the needed fields and derived the needed macros:
#[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,
}
This works fine until it gets to fields which contain a "null" value.
E.g. for the field "color": null,
in Helium.
The error message I get is { code: Message("invalid type: unit value, expected a string"), line: 8, column: 17 }
for this field.
I experimented with the #[serde(default)]
Macro. But this only works when the field is missing in the JSON file, not when there is a null
value.
I like to do the deserialization with the standard macros avoiding to program a Visitor Trait. Is there a trick I miss?
A deserialization error occurs because the struct definition is incompatible with the incoming objects: the
color
field can also benull
, as well as a string, yet giving this field the typeString
forces your program to always expect a string. This is the default behaviour, which makes sense. Be reminded thatString
(or other containers such asBox
) are not "nullable" in Rust. As for anull
value not triggering the default value instead, that is just how Serde works: if the object field wasn't there, it would work because you have added the default field attribute. On the other hand, a field "color" with the valuenull
is not equivalent to no field at all.One way to solve this is to adjust our application's specification to accept
null | string
, as specified by @user25064's answer:Playground with minimal example
Another way is to write our own deserialization routine for the field, which will accept
null
and turn it to something else of typeString
. This can be done with the attribute#[serde(deserialize_with=...)]
.Playground
Any field that can be null should be an
Option
type so that you can handle the null case. Something like this?