Кратко про Serde в Rust
Привет, Хабр!
Serde — это высокопроизводительная библиотека для сериализации и десериализации данных в Rust. Она поддерживает различные форматы данных, включая JSON, YAML, TOML, BSON и многие другие.
В этой статье рассмотрим основы Serde в Rust.
Установим
Для начала добавим Serde в проект. В файлике Cargo.toml
добавляем следующие строки в раздел [dependencies]
:
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
Здесь подключаем не только саму библиотеку Serde, но и serde_json
, которая даст вохможность работать с JSON-форматом. Функциональность Serde расширяется через различные адаптеры, так что в зависимости от нужд можно подключить и другие модули, такие как serde_yaml
или serde_toml
.
После добавления зависимостей запускаем команду cargo build
для загрузки и компиляции Serde вместе с проектом.
Основы работы с Serde
Сериализация — это процесс преобразования структур данных Rust в формат, который можно легко передавать или хранить. Десериализация — это обратный процесс, преобразование данных из формата обратно в структуры данных Rust.
Для старта работы с Serde добавляем атрибут #[derive(Serialize, Deserialize)]
к структурам данных. Это позволяет Serde автоматически генерировать код для сериализации и десериализации этих структур.
Пример сериализации и десериализации структуры в JSON:
use serde::{Serialize, Deserialize};
use serde_json::{to_string, from_str};
#[derive(Serialize, Deserialize)]
struct User {
id: u32,
name: String,
email: String,
}
fn main() {
// создание экземпляра структуры User
let user = User {
id: 1,
name: "Ivan Otus".to_string(),
email: "ivan.otus@example.com".to_string(),
};
// сериализация структуры User в JSON
let json = to_string(&user).unwrap();
println!("Serialized JSON: {}", json);
// десериализация JSON обратно в структуру User
let deserialized_user: User = from_str(&json).unwrap();
println!("Deserialized User: {:?}", deserialized_user);
}
Serde имеет различные аннотации и атрибуты для настройки процесса сериализации и десериализации. Самые используемые:
#[serde(rename = "new_name")]
: переименовывает поле при сериализации или десериализации.#[serde(default)]
: использует значение по умолчанию для поля, если оно отсутствует при десериализации.#[serde(skip_serializing)]
: пропускает поле при сериализации.#[serde(skip_deserializing)]
: пропускает поле при десериализации.#[serde(with = "module")]
: использует указанный модуль для сериализации и десериализации поля.
Попробуем использовать все сразу:
use serde::{Deserialize, Serialize};
use serde_with::serde_as;
#[serde_as]
#[derive(Serialize, Deserialize)]
struct User {
#[serde(rename = "userId")]
id: u32,
#[serde(default = "default_name")]
name: String,
#[serde(skip_serializing)]
password: String,
#[serde(skip_deserializing)]
secret: String,
#[serde(with = "serde_with::rust::display_fromstr")]
age: u32,
}
fn default_name() -> String {
"Unknown".to_string()
}
fn main() {
let user = User {
id: 1,
name: "Ivan Otus".to_string(),
password: "secret".to_string(),
secret: "hidden".to_string(),
age: 30,
};
let serialized = serde_json::to_string(&user).unwrap();
println!("Serialized: {}", serialized);
let deserialized: User = serde_json::from_str(&serialized).unwrap();
println!("Deserialized: {:?}", deserialized);
}
#[serde(rename = "userId")]
ренеймит поле id
в userId
при сериализации и десериализации.
#[serde(default = "default_name")]
юзает функцию default_name
для установки значения по умолчанию для поля name
, если оно отсутствует при десериализации.
#[serde(skip_serializing)]
скипает поле password
при сериализации, так что оно не будет включено в JSON-строку.
#[serde(skip_deserializing)]
пропускает поле secret
при десериализации, так что его значение останется неизменным после десериализации.
#[serde(with = "serde_with::rust::display_fromstr")]
использует модуль serde_with::rust::display_fromstr
для сериализации и десериализации поля age
. Годно для полей с пользовательскими типами, которые реализуют трейты Display
и FromStr
.
Кастомные сериализаторы и десериализаторы
В некоторых случаях может потребоваться более тонкая настройка процесса сериализации и десериализации, чем это позволяют стандартные механизмы Serde. В таких случаях можно использовать кастомные сериализаторы и десериализаторы.
Кастомный сериализатор — это функция или структура, реализующая трейт Serializer
из Serde. Аналогично, кастомный десериализатор реализует трейт Deserializer
.
Пример кастомного сериализатора для сериализации Option
в JSON как пустую строку, если значение None
:
use serde::{Serialize, Serializer};
fn serialize_option_string(value: &Option, serializer: S) -> Result
where
S: Serializer,
{
match value {
Some(v) => serializer.serialize_str(v),
None => serializer.serialize_str(""),
}
}
#[derive(Serialize)]
struct MyStruct {
#[serde(serialize_with = "serialize_option_string")]
name: Option,
}
fn main() {
let my_struct = MyStruct {
name: None,
};
let json = serde_json::to_string(&my_struct).unwrap();
println!("Serialized JSON: {}", json);
}
Допустим, есть структура Event
, и нужно сериализовать её в JSON, где дата будет представлена в формате «гггг-мм-дд»:
use serde::{Serialize, Serializer};
use chrono::{DateTime, Utc, NaiveDate};
struct Event {
name: String,
date: DateTime,
}
fn serialize_date(date: &DateTime, serializer: S) -> Result
where
S: Serializer,
{
let formatted_date = date.format("%Y-%m-%d").to_string();
serializer.serialize_str(&formatted_date)
}
impl Serialize for Event {
fn serialize(&self, serializer: S) -> Result
where
S: Serializer,
{
let mut state = serializer.serialize_struct("Event", 2)?;
state.serialize_field("name", &self.name)?;
state.serialize_field("date", &serialize_date(&self.date, serializer)?)?;
state.end()
}
}
fn main() {
let event = Event {
name: "RustConf".to_string(),
date: DateTime::from_utc(NaiveDate::from_ymd(2022, 9, 12).and_hms(0, 0, 0), Utc),
};
let serialized = serde_json::to_string(&event).unwrap();
println!("Serialized: {}", serialized);
}
Аналогично, можно создать кастомный десериализатор, который будет преобразовывать строку в формате «гггг-мм-дд» обратно в DateTime
:
use serde::{Deserialize, Deserializer};
use chrono::{DateTime, Utc, NaiveDate};
use serde::de::{self, Visitor};
use std::fmt;
struct Event {
name: String,
date: DateTime,
}
struct DateTimeVisitor;
impl<'de> Visitor<'de> for DateTimeVisitor {
type Value = DateTime;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a string in the format YYYY-MM-DD")
}
fn visit_str(self, value: &str) -> Result
where
E: de::Error,
{
NaiveDate::parse_from_str(value, "%Y-%m-%d")
.map(|date| DateTime::from_utc(date.and_hms(0, 0, 0), Utc))
.map_err(de::Error::custom)
}
}
fn deserialize_date<'de, D>(deserializer: D) -> Result, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_str(DateTimeVisitor)
}
#[derive(Deserialize)]
struct Event {
name: String,
#[serde(deserialize_with = "deserialize_date")]
date: DateTime,
}
fn main() {
let data = r#"{"name": "RustConf", "date": "2022-09-12"}"#;
let event: Event = serde_json::from_str(data).unwrap();
println!("Deserialized: {:?}", event);
}
Интеграция с другими либами
Serde может быть использована в Rocket для сериализации и десериализации данных, передаваемых в HTTP-запросах и ответах.
Пример использования Serde с Rocket для создания простого REST API:
#[macro_use] extern crate rocket;
use rocket::serde::{json::Json, Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
struct Task {
id: u32,
description: String,
}
#[post("/tasks", format = "json", data = "")]
fn create_task(task: Json) -> Json {
task
}
#[launch]
fn rocket() -> _ {
rocket::build()
.mount("/", routes![create_task])
}
Определяем структуру Task
, которая будет использоваться для сериализации и десериализации данных задач. создаем эндпойнт /tasks
, который принимает JSON-данные и возвращает их обратно клиенту.
Tokio — это асинхронный рантайм для Rust, который позволяет писать высокопроизводительные асинхронные приложения. Пример использования Serde с Tokio для асинхронного чтения и записи JSON-данных:
use tokio::fs::File;
use tokio::io::{self, AsyncReadExt, AsyncWriteExt};
use serde::{Deserialize, Serialize};
use serde_json;
#[derive(Serialize, Deserialize)]
struct User {
id: u32,
name: String,
}
async fn read_user_from_file(file_path: &str) -> io::Result {
let mut file = File::open(file_path).await?;
let mut contents = String::new();
file.read_to_string(&mut contents).await?;
let user: User = serde_json::from_str(&contents)?;
Ok(user)
}
async fn write_user_to_file(user: &User, file_path: &str) -> io::Result<()> {
let mut file = File::create(file_path).await?;
let contents = serde_json::to_string(user)?;
file.write_all(contents.as_bytes()).await?;
Ok(())
}
#[tokio::main]
async fn main() -> io::Result<()> {
let user = User {
id: 1,
name: "Ivan Otus".to_string(),
};
write_user_to_file(&user, "user.json").await?;
let read_user = read_user_from_file("user.json").await?;
println!("Read user: {:?}", read_user);
Ok(())
}
Определяем структуру User
и две асинхронные функции: read_user_from_file
для чтения пользователя из JSON-файла и write_user_to_file
для записи пользователя в JSON-файл. Юзаем Serde для сериализации и десериализации структуры User
.
Подробней с Serde можно ознакомиться в документации.
Про востребованные языки программирования и практические инструменты мои коллеги из OTUS рассказывают в рамках онлайн-курсов. По ссылке вы можете ознакомиться с полным каталогом курсов, а также записаться на открытые уроки.