Введение в JSON в Rust
JSON (JavaScript Object Notation) остаётся одним из самых популярных форматов обмена данными в веб-разработке, конфигурационных файлах и API. Язык Rust, известный своей производительностью и безопасностью памяти, предоставляет мощные инструменты для работы с JSON через крейт serde_json. В этой статье мы подробно разберём, как выполнять парсинг и сериализацию JSON в Rust, используя современные подходы и лучшие практики.
Библиотека serde (Serialization/Deserialization framework) является стандартом де-факто для работы с форматами данных в экосистеме Rust. В сочетании с serde_json она позволяет легко преобразовывать структуры Rust в JSON и обратно.
Настройка проекта и установка зависимостей
Для начала работы создайте новый проект Cargo и добавьте необходимые зависимости в файл Cargo.toml:
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
Флаг features = ["derive"] для крейта serde включает макросы #[derive(Serialize, Deserialize)], которые автоматически генерируют код для сериализации и десериализации структур.
Основы парсинга JSON в Rust
Парсинг строки JSON в динамическое значение
Самый простой способ разобрать JSON — использовать тип serde_json::Value, который представляет собой универсальное JSON-значение:
use serde_json::Value;
fn main() {
let data = r#"
{
"name": "Алексей",
"age": 30,
"is_student": false,
"hobbies": ["чтение", "программирование", "велосипед"]
}"#;
let parsed: Value = serde_json::from_str(data).expect("Ошибка парсинга JSON");
println!("Имя: {}", parsed["name"]);
println!("Возраст: {}", parsed["age"]);
println!("Студент: {}", parsed["is_student"]);
println!("Первое хобби: {}", parsed["hobbies"][0]);
}
Тип Value — это перечисление, которое может содержать Null, Bool, Number, String, Array или Object. Это удобно для быстрого прототипирования, но для production-кода лучше использовать строго типизированные структуры.
Парсинг JSON в строго типизированные структуры
Для безопасной работы с данными определите структуру, которая соответствует JSON-схеме, и используйте макросы serde:
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
struct Person {
name: String,
age: u32,
is_student: bool,
hobbies: Vec<String>,
}
fn main() {
let json_str = r#"
{
"name": "Мария",
"age": 25,
"is_student": true,
"hobbies": ["рисование", "музыка"]
}"#;
let person: Person = serde_json::from_str(json_str)
.expect("Ошибка десериализации");
println!("{:?}", person);
println!("{} учится: {}", person.name, person.is_student);
}
Преимущество этого подхода — компилятор проверяет типы на этапе компиляции, что исключает ошибки времени выполнения, связанные с неверным доступом к полям.
Сериализация данных в JSON
Преобразование структур в строку JSON
Сериализация выполняется с помощью функции serde_json::to_string():
use serde::{Serialize, Deserialize};
#[derive(Debug, Serialize, Deserialize)]
struct Product {
id: u64,
name: String,
price: f64,
in_stock: bool,
}
fn main() {
let product = Product {
id: 42,
name: "Ноутбук".to_string(),
price: 89999.99,
in_stock: true,
};
let json_string = serde_json::to_string(&product)
.expect("Ошибка сериализации");
println!("JSON: {}", json_string);
// Красивый вывод с отступами
let pretty_json = serde_json::to_string_pretty(&product)
.expect("Ошибка форматирования");
println!("Форматированный JSON:\n{}", pretty_json);
}
Функция to_string_pretty() добавляет отступы и переносы строк, что удобно для чтения человеком или записи в конфигурационные файлы.
Сериализация коллекций и вложенных структур
Rust и serde_json отлично работают с векторами, хеш-картами и вложенными структурами:
use serde::{Serialize, Deserialize};
use std::collections::HashMap;
#[derive(Debug, Serialize, Deserialize)]
struct Order {
order_id: String,
items: Vec<OrderItem>,
total: f64,
}
#[derive(Debug, Serialize, Deserialize)]
struct OrderItem {
product_name: String,
quantity: u32,
price: f64,
}
fn main() {
let order = Order {
order_id: "ORD-2024-001".to_string(),
items: vec![
OrderItem {
product_name: "Книга".to_string(),
quantity: 2,
price: 1500.0,
},
OrderItem {
product_name: "Ручка".to_string(),
quantity: 5,
price: 120.0,
},
],
total: 3600.0,
};
let json = serde_json::to_string_pretty(&order).unwrap();
println!("Заказ в JSON:\n{}", json);
// Работа с HashMap
let mut config = HashMap::new();
config.insert("host".to_string(), "localhost".to_string());
config.insert("port".to_string(), "8080".to_string());
let config_json = serde_json::to_string(&config).unwrap();
println!("Конфиг: {}", config_json);
}
Продвинутые техники работы с JSON
Обработка опциональных полей и значений по умолчанию
Часто JSON содержит необязательные поля. Serde позволяет гибко это обрабатывать:
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
struct UserProfile {
username: String,
#[serde(default)]
full_name: Option<String>,
#[serde(default = "default_age")]
age: u32,
#[serde(default)]
email: Option<String>,
}
fn default_age() -> u32 {
18
}
fn main() {
let json = r#"{"username": "ivan_petrov"}"#;
let profile