This is the second part of the 2 part post that deals with decoding JSON strings into Rust values. First part is available here.
As with serialization,
base64 modules are not relevant to JSON
deserialization, so we should not pay attention to them.
In order for a type to be decodable from JSON, it must implement Decodable trait. Almost all built-in types already implement it, so you can deserialize them with:
1 2 3 4 5 6 7 8 9 10 11 12 13
Deserializing to Option is somewhat redundant, because
DecodeResult, which is a type alias
for a regular result. That means you can pattern match on DecodeResult
and handle potential failure.
1 2 3 4 5
Decoding JSON to a tuple is identical to vector, just specify the correct type:
1 2 3 4 5
1 2 3 4 5 6 7 8 9 10
As with serializing, Rust cannot automatically deserialize JSON string into a fixed-length array. The reason for this is the same: arrays’ type signature contain length as part of the type, but Rust currently (and most likely not until after v1.0) can’t be generic over array’s length.
1 2 3 4 5 6
To remedy this, we will use custom decoding, as we did with custom array encoding in part 1. I’ll show an example of this below.
As with serialization, it’s possible to have Rust deserialize structs
automatically for you. You will need to add the
#[deriving(Decodable)] attribute to your struct:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Note that I’m using 3 deriving trait implementations for a struct:
Show. This is to make my struct fully
JSON (de)serializable and printable automatically.
This is probably the cornerstone of the JSON infrastructure. In real life you often cannot control the shape of the JSON that comes to you, so you must be able to convert arbitrary JSON strings into your objects. Luckily, Rust decoding capabilities will help us here.
Let us continue with our
Person struct example and deserialize the
object from a complex JSON where our object is in the
key. The example might be contrived, but it serves the demo purpose.
To make a type JSON deserializable we need to implement the
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
Let’s break down the code line by line to see what’s going on here.
Line 2: We need to bring both
Decoder traits into
Decodable trait is for the struct to implement, to conform to
the JSON deserialization interface, while the
Decoder is the low level workhorse of deserialization, which tokenizes
and parses the JSON string to convert it to Rust values later.
Line 5: I’m using a raw string literal to avoid escaping double quotes.
Line 6: The line where I’m decoding JSON string into an instance of
Person struct. Note that I need to type-annotate the variable when
Line 10: We no longer need to use the
attribute, because we implement the
Decodable trait ourselves.
Line 16: This is the
Decodable implementation. It is very similar to
Encodable implementation from part 1 with the exception of
type restricted to
Decoder trait now.
Line 17: Two differences from
encode method counterpart:
we’re no longer accepting
&self as the first argument,
decode is an associated function, rather than a method.
The analogy is class methods in Ruby or static methods
in Java. Second difference is the return type. It is now
Line 18: This is where the parsing starts. We do actual parsing with
read_* family of methods on
Decoder instance. Here we’re reading the
top-level struct with
read_struct method. First argument is the name
of the sturct(not used), second is the length (not applicable). The third argument is an
instance of anonymous function (lambda). Why are the first and the
second arguments not used?
I think this is because the entire family of
read_* methods of
Encoder strives to be uniform and thus a unified set of arguments
is used, even when the encoder does not need them.
You can think of the
read_struct call as “opening” the top-level JSON
object to be able to move inside to read actual values. The lambda is
where we descend and continue with reading.
Line 19: The object we’re trying to read is in the
data field, so
we’re reading it on this line with
This time the first argument is necessary, because it tells the decoder
the actual name of the field. 3rd argument is the lambda again to
descend further into the the object in the
Lines 20-21: Field reading happens here. By this time the parser
has reached the contents of the
data object so we can now just read the
fields we’re interested in one-by-one. We’re using
again, passing it the name of the field and the index(not used).
The third argument is the value of the field, correctly decoded from
JSON representation. Since all primitive values in Rust already
Decodable trait, we can safely call
on them to deserialize them as the
Person struct fields.
Deserializing fixed length arrays
As in part 1, let’s use this knowledge to deserialize a fixed-length array from JSON.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
Since rust will not allow to provide the implementation of a trait for a type where both the trait and the type were defined in the external crate, we need to create a tuple struct (newtype) for the array.
Overall, this implementation looks similar to the previous, but there are nuances I’d like to point out.
Line 18: Note that the implementation signature adds new
T type parameter,
which is the type of the array. It can be anything that implements
Encodable<S, E> traits.
Default is to be able to fill array with default values (line 24).
Copy is to be able to copy the default values
into the new array, while the
Decodable<S, E> is to be able to decode
the array elements from JSON.
Lines 22-24: Here I’m checking if the array we’re about to decode contains
exactly the number of elements we expect. If not, I exit early with an
Lines 26-28: Here I’m iterating the array, obtaining mutable
references to its elements and filling them from JSON, using
Line 29: Here I’m returning the result wrapped in
As in part 1, let’s look at the expanded implementation of the
Let’s use the
Person struct example again
and compile it with
--pretty expanded flag:
rustc app.rs --pretty expanded
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
The output is very similar to the manual deserialization code we
saw earlier, except that compiler further expanded the
try! macros into
There is a convenience
trait that allows implementors to quickly convert itself into JSON using
intermediate Json enum representation, but I recommend using it only for small and relatively simple data structures.