Введение: Почему JSON важен для Rust-разработчика
JSON (JavaScript Object Notation) остаётся одним из самых популярных форматов обмена данными в веб-разработке, микросервисах и API. Для языка Rust, который стремительно набирает популярность благодаря своей производительности и безопасности, работа с JSON является критически важной задачей. Будь вы разработчиком бэкенда на Actix или Rocket, пишете CLI-утилиту или работаете с веб-сокетами — умение парсить и сериализовать JSON в Rust обязательно пригодится.
В этой статье мы разберём, как эффективно работать с JSON в Rust, используя стандартную экосистему крейтов serde и serde_json. Вы узнаете, как преобразовывать структуры данных в JSON и обратно, обрабатывать ошибки и работать с динамическими данными.
Основы: Установка и подключение зависимостей
Для работы с JSON в Rust используется мощный фреймворк сериализации Serde и его реализация для JSON — крейт serde_json. Чтобы начать, добавьте их в ваш Cargo.toml:
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
Флаг features = ["derive"] позволяет использовать макросы #[derive(Serialize, Deserialize)], что значительно упрощает код.
Базовый парсинг JSON строки
Самый простой способ начать — распарсить JSON-строку в универсальный тип serde_json::Value. Это аналог serde_json::Value в Python или object в JavaScript:
use serde_json::Value;
fn main() {
let data = r#"
{
"name": "Rust",
"year": 2015,
"features": ["safe", "fast", "concurrent"]
}"#;
let parsed: Value = serde_json::from_str(data).expect("Ошибка парсинга JSON");
println!("Имя: {}", parsed["name"]);
println!("Год: {}", parsed["year"]);
println!("Первый feature: {}", parsed["features"][0]);
}
Тип Value может быть: Null, Bool, Number, String, Array или Object. Это удобно для прототипирования, но для production-кода лучше использовать строгую типизацию.
Сериализация и десериализация структур
Главная сила Serde — автоматическое преобразование между Rust-структурами и JSON. Рассмотрим на примере:
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug)]
struct User {
id: u32,
name: String,
email: String,
active: bool,
}
fn main() {
// Десериализация (JSON -> Rust)
let json_str = r#"{"id":1,"name":"Алексей","email":"alex@example.com","active":true}"#;
let user: User = serde_json::from_str(json_str).unwrap();
println!("Пользователь: {:?}", user);
// Сериализация (Rust -> JSON)
let new_user = User {
id: 2,
name: String::from("Мария"),
email: String::from("maria@example.com"),
active: false,
};
let json_output = serde_json::to_string_pretty(&new_user).unwrap();
println!("JSON:\n{}", json_output);
}
Обратите внимание на to_string_pretty — он форматирует JSON с отступами для читаемости. Для production используйте to_string (без лишних пробелов).
Работа с вложенными структурами
JSON часто содержит вложенные объекты и массивы. Serde легко с этим справляется:
#[derive(Serialize, Deserialize)]
struct Project {
name: String,
languages: Vec<String>,
contributors: Vec<Contributor>,
}
#[derive(Serialize, Deserialize)]
struct Contributor {
username: String,
contributions: u32,
}
fn main() {
let project_json = r#"
{
"name": "Serde",
"languages": ["Rust", "Python", "C++"],
"contributors": [
{"username": "dtolnay", "contributions": 1500},
{"username": "erickt", "contributions": 800}
]
}"#;
let project: Project = serde_json::from_str(project_json).unwrap();
println!("Проект: {}, участников: {}", project.name, project.contributors.len());
}
Продвинутые техники: кастомные имена полей и обработка ошибок
Изменение имён полей при сериализации
Иногда имена полей в Rust-структуре не совпадают с ключами JSON. Используйте атрибут #[serde(rename)]:
#[derive(Serialize, Deserialize)]
struct Config {
#[serde(rename = "max_connections")]
max_connections: u32,
#[serde(rename = "timeout_sec")]
timeout_sec: u64,
#[serde(rename = "enable_logging")]
enable_logging: bool,
}
// JSON: {"max_connections": 100, "timeout_sec": 30, "enable_logging": true}
Также можно использовать #[serde(rename_all = "camelCase")] для автоматического преобразования всех полей.
Обработка ошибок парсинга
Всегда используйте осмысленную обработку ошибок вместо .unwrap():
use serde_json::Error;
fn parse_user(json_str: &str) -> Result<User, Error> {
let user: User = serde_json::from_str(json_str)?;
Ok(user)
}
fn main() {
let bad_json = r#"{"id":"не число","name":123}"#;
match parse_user(bad_json) {
Ok(user) => println!("Успех: {:?}", user),
Err(e) => eprintln!("Ошибка парсинга: {}", e),
}
}
Работа с динамическими JSON (без структуры)
Если структура JSON заранее неизвестна или постоянно меняется, используйте serde_json::Value с методами доступа:
use serde_json::{Value, json};
fn main() {
// Создание JSON на лету
let data = json!({
"status": "ok",
"data": {
"items": [1, 2, 3],
"total": 100
}
});
// Доступ к вложенным полям
if let Some(items) = data["data"]["items"].as_array() {
println!("Количество элементов: {}", items.len());
}
// Проверка типа
if data["status"].as_str() == Some("ok") {