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

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

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

Начать курс

Введение: почему JSON важен в Rust

JSON (JavaScript Object Notation) — это легкий формат обмена данными, который стал стандартом де-факто для веб-API, конфигурационных файлов и межсервисного взаимодействия. В Rust, языке системного программирования с фокусом на безопасность и производительность, работа с JSON реализуется через мощную экосистему крейтов (библиотек).

Несмотря на то, что Rust не имеет встроенной поддержки JSON (в отличие от JavaScript или Python), его система типов и подход к управлению памятью делают обработку JSON не только безопасной, но и невероятно быстрой. В этой статье мы разберем, как парсить JSON в структуры Rust, как сериализовать данные обратно в JSON и как избежать типичных ошибок новичков.

Основным инструментом для работы с JSON в Rust является крейт serde (Serialization/Deserialization framework) в паре с serde_json. Эти библиотеки обеспечивают нулевые накладные расходы (zero-cost abstractions) и поддерживают сложные вложенные структуры данных.

Установка и настройка зависимостей

Перед тем как начать, создайте новый проект Rust и добавьте необходимые зависимости в файл Cargo.toml:

[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

Флаг features = ["derive"] для крейта serde позволяет использовать макросы #[derive(Serialize, Deserialize)], которые автоматически генерируют код для преобразования ваших структур в JSON и обратно. Это ключевая особенность Rust — вы пишете минимум шаблонного кода, а компилятор берет на себя рутинную работу.

После добавления зависимостей выполните cargo build, чтобы загрузить и скомпилировать библиотеки.

Парсинг JSON в структуры Rust (десериализация)

Создание модели данных

Первое, что нужно сделать — определить структуры Rust, которые соответствуют вашим JSON-данным. Допустим, у нас есть JSON, представляющий информацию о пользователе:

{
  "name": "Алексей",
  "age": 30,
  "email": "alexey@example.com",
  "is_active": true
}

Создадим соответствующую структуру и добавим атрибуты для сериализации:

use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
struct User {
    name: String,
    age: u32,
    email: String,
    is_active: bool,
}

Макрос #[derive(Debug)] позволяет выводить структуру в консоль для отладки. Без него вы не сможете использовать println!("{:?}", user).

Парсинг строки JSON

Теперь напишем функцию, которая принимает строку JSON и возвращает структуру User:

fn parse_user(json_str: &str) -> Result<User, serde_json::Error> {
    let user: User = serde_json::from_str(json_str)?;
    Ok(user)
}

fn main() {
    let data = r#"
        {
            "name": "Алексей",
            "age": 30,
            "email": "alexey@example.com",
            "is_active": true
        }
    "#;

    match parse_user(data) {
        Ok(user) => println!("Пользователь: {:?}", user),
        Err(e) => eprintln!("Ошибка парсинга: {}", e),
    }
}

Ключевой момент: serde_json::from_str автоматически сопоставляет поля JSON с полями структуры. Если имена полей в JSON отличаются от имен в Rust (например, JSON использует snake_case, а Rust — camelCase), можно использовать атрибут #[serde(rename = "имя_поля")].

Обработка вложенных структур

JSON часто содержит вложенные объекты. Рассмотрим пример с адресом пользователя:

{
  "name": "Алексей",
  "address": {
    "city": "Москва",
    "street": "Тверская",
    "zip": "101000"
  }
}

Создадим вложенные структуры:

#[derive(Debug, Serialize, Deserialize)]
struct Address {
    city: String,
    street: String,
    zip: String,
}

#[derive(Debug, Serialize, Deserialize)]
struct UserWithAddress {
    name: String,
    address: Address,
}

Парсинг работает точно так же — serde рекурсивно обрабатывает все вложенные структуры. Это одна из сильных сторон библиотеки: она поддерживает произвольную глубину вложенности без дополнительных настроек.

Сериализация: преобразование структур Rust в JSON

Обратный процесс — сериализация — не менее важен. Когда ваша программа должна отправить данные во внешний API или сохранить их в файл, вам нужно превратить структуры Rust обратно в JSON.

fn main() {
    let user = User {
        name: String::from("Мария"),
        age: 25,
        email: String::from("maria@example.com"),
        is_active: true,
    };

    // Сериализация в строку JSON
    match serde_json::to_string_pretty(&user) {
        Ok(json) => println!("Сериализованный JSON:\n{}", json),
        Err(e) => eprintln!("Ошибка сериализации: {}", e),
    }
}

Метод to_string_pretty создает форматированный JSON с отступами для читаемости. Если вам нужна компактная строка (без пробелов), используйте serde_json::to_string.

Запись JSON в файл

Часто требуется сохранить данные в файл. Rust предоставляет стандартные средства для работы с файловой системой:

use std::fs::File;
use std::io::Write;

fn save_user_to_file(user: &User, filename: &str) -> Result<(), Box<dyn std::error::Error>> {
    let json = serde_json::to_string_pretty(user)?;
    let mut file = File::create(filename)?;
    file.write_all(json.as_bytes())?;
    Ok(())
}

fn main() {
    let user = User {
        name: String::from("Иван"),
        age: 35,
        email: String::from("ivan@example.com"),
        is_active: false,
    };

    if let Err(e) = save_user_to_file(&user, "user.json") {
        eprintln!("Ошибка сохранения: {}", e);
    }
}

Обратите внимание на использование Box<dyn std::error::Error> — это трейт-объект, позволяющий возвращать ошибки разных типов из одной функции.

Работа с динамическими JSON-данными

Иногда

Блоги

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