When serde_json::to_string() fails
Serhii Potapov June 18, 2022 #rust #json #serdeI've been using serde and serde_json for a few years almost in every Rust project I had.
It's pretty clear why serde_json::from_str() returns Result
,
many things that can go wrong on deserialization (malformed JSON, missing fields, wrong field types, etc.).
But what about serde_json::to_string()? This one returns Result
too.
In my practice, I've never seen (until very recently) serialization errors, and logically thinking:
if a data structure cannot be serialized, why should it be compiled at all?
The code
Let's say we're working on an app to store the preferences of our friends. For the sake of simplicity, we'll focus only on a limited set of drinks.
use ;
This prints the following JSON:
Notice, the .unwrap()
.
We know for sure it will never panic: we can have only 3 different drinks: mate, sparkling water and still water.
None of these variants will ever break the serialization. That's guaranteed.
Over time the code evolves
As time goes by, all of our friends switched their nutrition from sparkling water to still water.
You've decided that sparkling: bool
is obsolete.
Also, we realized that we need to track where a particular drink is stored in the kitchen. For that, we introduce a new enum Location
and we keep track of the drinks as HashMap<Drink, Location>
.
Of course, to be able to use Drink
as the key for the hashmap it should derive Hash
, PartialEq
and Eq
:
The following snippet illustrates the changes and tries to serialize HashMap<Drink, Location>
.
use ;
use HashMap;
Oh, but this time serialization fails!
thread 'main' panicked at 'called `Result::unwrap()`
on an `Err` value: Error("key must be a string", line: 0, column: 0)'
¡Qué pena!
What does it mean "key must be a string"
?
How such a simple enum is not a string?
Let's proof, that it's serialized to string:
let json = to_string.unwrap;
println!;
Oh. It's serialized to object.
While refactoring, we completely forgot that #[serde(tag = "t")]
makes a simple enum serialize as a JSON object,
and therefore it cannot be used as a JSON key.
Albeit it seems to be obvious and reasonable now, unfortunately, the compiler could not prevent the error, and it was encountered only at runtime.
Summary
Luckily such an error was very easy to detect. Clearly, it was an error caused by our code and not by data.
We all love to think that "if it compiles it works", but it's only true until it's not. There are certain programming techniques that allows us to move some errors from runtime to compile time, but we should not give up on automated and manual testing.