Работа с JSON в Rust: парсинг и сериализация данных

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: Почему 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") {                

Блоги

Рекомендаций по книгам