Привет, Хабр!
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<String> в JSON как пустую строку, если значение None:
use serde::{Serialize, Serializer}; fn serialize_option_string<S>(value: &Option<String>, serializer: S) -> Result<S::Ok, S::Error> 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<String>, } 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<Utc>, } fn serialize_date<S>(date: &DateTime<Utc>, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer, { let formatted_date = date.format("%Y-%m-%d").to_string(); serializer.serialize_str(&formatted_date) } impl Serialize for Event { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 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<Utc>:
use serde::{Deserialize, Deserializer}; use chrono::{DateTime, Utc, NaiveDate}; use serde::de::{self, Visitor}; use std::fmt; struct Event { name: String, date: DateTime<Utc>, } struct DateTimeVisitor; impl<'de> Visitor<'de> for DateTimeVisitor { type Value = DateTime<Utc>; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a string in the format YYYY-MM-DD") } fn visit_str<E>(self, value: &str) -> Result<Self::Value, E> 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<DateTime<Utc>, D::Error> where D: Deserializer<'de>, { deserializer.deserialize_str(DateTimeVisitor) } #[derive(Deserialize)] struct Event { name: String, #[serde(deserialize_with = "deserialize_date")] date: DateTime<Utc>, } 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 = "<task>")] fn create_task(task: Json<Task>) -> Json<Task> { 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<User> { 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 рассказывают в рамках онлайн-курсов. По ссылке вы можете ознакомиться с полным каталогом курсов, а также записаться на открытые уроки.
ссылка на оригинал статьи https://habr.com/ru/articles/806247/
Добавить комментарий