Введение в работу с JSON в Rust
JSON (JavaScript Object Notation) — один из самых популярных форматов обмена данными в веб-разработке, конфигурационных файлах и API. Если вы изучаете Rust и хотите научиться эффективно работать с JSON, эта статья для вас. Мы рассмотрим основные библиотеки, принципы парсинга и сериализации, а также разберём практические примеры кода.
В Rust экосистема для работы с JSON построена вокруг крейта serde — мощного фреймворка для сериализации и десериализации данных. В паре с serde_json он позволяет легко преобразовывать структуры данных в JSON и обратно. Давайте разберёмся, как это работает.
Установка и настройка зависимостей
Для начала создадим новый проект и добавим необходимые зависимости в файл Cargo.toml:
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
Крейт serde предоставляет макросы #[derive(Serialize, Deserialize)], которые автоматически реализуют необходимые типажи для ваших структур. serde_json отвечает непосредственно за работу с JSON-форматом.
Парсинг JSON: из строки в структуру
Допустим, у нас есть JSON-строка с информацией о пользователе. Мы хотим преобразовать её в типизированную структуру Rust. Вот как это делается:
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize)]
struct User {
name: String,
age: u32,
email: String,
is_active: bool,
}
fn main() {
let json_data = r#"
{
"name": "Алексей",
"age": 28,
"email": "alex@example.com",
"is_active": true
}
"#;
// Парсинг JSON в структуру
let user: User = serde_json::from_str(json_data).expect("Ошибка парсинга JSON");
println!("Пользователь: {:?}", user);
println!("Имя: {}", user.name);
println!("Возраст: {}", user.age);
}
Обратите внимание на атрибут #[derive(Debug, Deserialize, Serialize)]. Debug позволяет выводить структуру в консоль, а Deserialize и Serialize дают возможность преобразовывать данные в JSON и обратно.
Обработка вложенных объектов
JSON часто содержит вложенные объекты. Рассмотрим пример с адресом пользователя:
#[derive(Debug, Deserialize, Serialize)]
struct Address {
city: String,
street: String,
zip_code: String,
}
#[derive(Debug, Deserialize, Serialize)]
struct User {
name: String,
age: u32,
address: Address,
}
fn main() {
let json_data = r#"
{
"name": "Мария",
"age": 32,
"address": {
"city": "Москва",
"street": "Тверская, 15",
"zip_code": "101000"
}
}
"#;
let user: User = serde_json::from_str(json_data).unwrap();
println!("Город: {}", user.address.city);
}
Работа с массивами JSON
Массивы в JSON преобразуются в Vec или другие коллекции Rust:
use serde::Deserialize;
#[derive(Debug, Deserialize)]
struct Product {
id: u32,
name: String,
price: f64,
}
fn main() {
let json_array = r#"
[
{"id": 1, "name": "Ноутбук", "price": 75000.50},
{"id": 2, "name": "Мышь", "price": 1500.00},
{"id": 3, "name": "Клавиатура", "price": 3500.00}
]
"#;
let products: Vec<Product> = serde_json::from_str(json_array).unwrap();
for product in &products {
println!("{}: {} руб.", product.name, product.price);
}
}
Сериализация: из структуры в JSON
Обратный процесс — преобразование структуры Rust в JSON-строку. Это называется сериализацией:
use serde::Serialize;
#[derive(Debug, Serialize)]
struct Config {
host: String,
port: u16,
debug: bool,
allowed_origins: Vec<String>,
}
fn main() {
let config = Config {
host: String::from("127.0.0.1"),
port: 8080,
debug: true,
allowed_origins: vec![
String::from("http://localhost:3000"),
String::from("https://myapp.com"),
],
};
// Красивый вывод с отступами
let json_pretty = serde_json::to_string_pretty(&config).unwrap();
println!("{}", json_pretty);
// Компактный вывод без пробелов
let json_compact = serde_json::to_string(&config).unwrap();
println!("{}", json_compact);
}
Метод to_string_pretty создаёт читаемый JSON с отступами, а to_string — компактную строку для передачи по сети.
Продвинутые техники работы с JSON
Изменение имён полей
Иногда имена полей в JSON отличаются от имён переменных в Rust. Используйте атрибут #[serde(rename)]:
#[derive(Debug, Deserialize, Serialize)]
struct ApiResponse {
#[serde(rename = "user_id")]
user_id: u32,
#[serde(rename = "full_name")]
full_name: String,
#[serde(rename = "created_at")]
created_at: String,
}
Опциональные поля
Не все поля JSON могут присутствовать. Используйте Option для обработки таких случаев:
#[derive(Debug, Deserialize)]
struct User {
name: String,
age: u32,
#[serde(default)]
email: Option<String>, // Поле может отсутствовать
#[serde(default = "default_phone")]
phone: String,
}
fn default_phone() -> String {
String::from("Не указан")
}
Работа с произвольным JSON
Если структура JSON заранее неизвестна, используйте тип serde_json::Value:
use serde_json::Value;
fn main() {
let data = r#"
{
"name": "Тест",
"values": [1, 2, 3],
"nested": {"key": "value"},
"flag": true
}
"#;
let v: Value = serde_json::from_str(data).unwrap();