JSON Processing in Rust: Complete Guide with serde_json
JSON (JavaScript Object Notation) is one of the most popular data exchange formats in web development. In Rust, JSON processing is done using the powerful serde_json library, which is part of the Serde ecosystem — a framework for serializing and deserializing data structures. In this article, we will thoroughly examine how to work with JSON in Rust: from installation to complex examples with nested structures.
Installation and Setup
To get started, add the dependencies to the Cargo.toml file:
[dependencies]serde = { version = "1.0", features = ["derive"] }serde_json = "1.0"The serde library provides the #[derive(Serialize, Deserialize)] macros, and serde_json handles converting data to and from JSON.
Basic Serialization and Deserialization
Let's look at a simple example: create a User struct and learn how to convert it to JSON and back.
use serde::{Serialize, Deserialize};use serde_json;
#[derive(Debug, Serialize, Deserialize)]struct User { name: String, age: u8, email: String,}
fn main() { // Create an instance of the struct let user = User { name: "Alexey".to_string(), age: 30, email: "alex@example.com".to_string(), };
// Serialize to JSON string let json_string = serde_json::to_string(&user).unwrap(); println!("JSON: {}", json_string);
// Deserialize from JSON back to struct let deserialized_user: User = serde_json::from_str(&json_string).unwrap(); println!("Deserialized: {:?}", deserialized_user);}Output:
JSON: {"name":"Alexey","age":30,"email":"alex@example.com"}Deserialized: User { name: "Alexey", age: 30, email: "alex@example.com" }Working with Nested Structures
JSON often contains nested objects and arrays. Rust makes it easy to model such structures:
#[derive(Debug, Serialize, Deserialize)]struct Address { city: String, street: String, house: u16,}
#[derive(Debug, Serialize, Deserialize)]struct Person { name: String, age: u8, address: Address, hobbies: Vec<String>,}
fn main() { let person = Person { name: "Maria".to_string(), age: 25, address: Address { city: "Moscow".to_string(), street: "Tverskaya".to_string(), house: 10, }, hobbies: vec!["reading".to_string(), "sports".to_string()], };
// Pretty-printed JSON output with indentation let pretty_json = serde_json::to_string_pretty(&person).unwrap(); println!("{}", pretty_json);}Output:
{ "name": "Maria", "age": 25, "address": { "city": "Moscow", "street": "Tverskaya", "house": 10 }, "hobbies": [ "reading", "sports" ]}Error Handling and Optional Fields
In real projects, data may be incomplete. Rust offers elegant ways to handle such situations:
- Option<T> — for optional fields
- Result<T, E> — for handling parsing errors
- Serde attributes — for customizing serialization behavior
use serde::{Serialize, Deserialize};use serde_json::Result;
#[derive(Debug, Serialize, Deserialize)]struct Product { name: String, price: f64, #[serde(default)] // Default value if field is missing description: Option<String>, #[serde(skip_serializing_if = "Option::is_none")] // Skip if None discount: