Работа с JSON в Rust: парсинг, сериализация и практические примеры

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

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

Начать курс

Введение в 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                

Блоги

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