JSON support is very much production-ready and arguably best in class compared to other mainstream languages.
JSON has become one of the most common data exchange formats on the web, so it’s critical that server-side languages have good support for it. Fortunately, dealing with JSON is an area where Rust shines, thanks in large part to the serde and serde_json crates. These are among the most battle-tested crates in the Rust ecosystem and serve as great examples of ways to exploit Rust’s high-level abstractions while maintaining low-level control.
While there are plenty of other JSON crates available, serde_json is by far the most popular. The large ecosystem built around serde makes it the top choice for web servers written in Rust.
In this tutorial, we’ll explore serde_json and demonstrate how to use Rust’s type system to express JSON data.
To get started with serde_json, you must first implement the Serialize
and Deserialize
traits on your types. Thanks to derive macros, this is really trivial for most types. To use derive macros, make sure you enable the “derive” feature flag for serde in your dependencies.
## cargo.toml
[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
Now we can use them like any other derive macro.
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize)]
struct Person {
name: String,
age: usize,
verified: bool,
}
That’s all you need to do to make Person
serializable and deserializable into any data format with a crate that supports serde. Debug is optional, but we’ll use it for demonstration purposes. Converting a JSON string into an instance of Person
is now as simple as calling serde_json::from_str
.
fn main() {
let json = r#"
{
"name": "George",
"age": 27,
"verified": false
}
"#;
let person: Person = serde_json::from_str(json).unwrap();
println!("{:?}", person);
}
There are a couple of things to point out here, the first being the explicit Person
type annotation. In this example, there is no way for the compiler to infer the type of person
; it could potentially be any type that implements Deserialize
. In more complete examples it would be inferred from things like function argument types when passing around person
.
The other thing to note is the call to unwrap()
. Deserialization can fail in a number of ways, so serde_json::from_str
returns a Result
to let us handle those failures. Errors from serde_json are quite rich and give us enough information to pin down exactly what went wrong. For example, running the same code as above with the age
field removed triggers the following error message.
Error("missing field `age`", line: 5, column: 9)
You would get a similar message if there were syntax errors in the JSON. Instead of using unwrap
, you can extract the same information seen above by using the methods provided by serde_json::Error
and handle the error gracefully whenever possible.
One of my favorite things about using JSON in Rust is that it provides complete type checking with zero boilerplate code and error handling that is enforced at compile time thanks to the Result
type. The places where you use JSON are almost always at system boundaries where you can receive all kinds of unexpected inputs. Having first-class, consistent support for error handling makes dealing with these system boundaries much more enjoyable and reliable.
#rust #json #javascript #programming #developer