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?
Null valuesJSON has a special value called null which can be set on any type of data including arrays, objects, number and boolean types.
The Hive JSON SerDe is commonly used to process JSON data like events. These events are represented as single-line strings of JSON-encoded text separated by a new line. The Hive JSON SerDe does not allow duplicate keys in map or struct key names.
Serde is a framework for serializing and deserializing Rust data structures efficiently and generically. The Serde ecosystem consists of data structures that know how to serialize and deserialize themselves along with data formats that know how to serialize and deserialize other things.
A deserialization error occurs because the struct definition is incompatible with the incoming objects: the color
field can also be null
, as well as a string, yet giving this field the type String
forces your program to always expect a string. This is the default behaviour, which makes sense. Be reminded that String
(or other containers such as Box
) are not "nullable" in Rust. As for a null
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 value null
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:
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Element {
color: Option<String>,
}
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 type String
. This can be done with the attribute #[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
See also:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With