When serde_json::to_string() failsSergey Potapov June 18, 2022 #rust #json #serde
I'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
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
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?
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.
This prints the following JSON:
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
and we keep track of the drinks as
Of course, to be able to use
Drink as the key for the hashmap it should derive
The following snippet illustrates the changes and tries to serialize
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)'
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.
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.
Back to top