Loading page…
Rust walkthroughs
Loading page…
serde using Serializer and Deserializer traits?Serde's Serializer and Deserializer traits define the interface between Rust data structures and data formats like JSON, YAML, or binary formats. Implementing these traits gives you complete control over how types are serialized and deserialized, enabling custom encoding schemes, format-specific optimizations, or handling types that don't derive Serialize and Deserialize automatically. The traits are large but follow consistent patterns.
use serde::{Serialize, Serializer, ser::SerializeStruct};
// Custom serialization for a type
struct User {
id: u64,
username: String,
email: String,
is_admin: bool,
}
impl Serialize for User {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
// Serialize as a struct with named fields
let mut s = serializer.serialize_struct("User", 4)?;
s.serialize_field("id", &self.id)?;
s.serialize_field("username", &self.username)?;
s.serialize_field("email", &self.email)?;
s.serialize_field("is_admin", &self.is_admin)?;
s.end()
}
}The Serialize trait controls how a type produces output for any format.
use serde::{Deserialize, Deserializer, de::Error as DeError};
use std::fmt;
struct User {
id: u64,
username: String,
email: String,
is_admin: bool,
}
impl<'de> Deserialize<'de> for User {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
// Define a visitor to handle the deserialization
struct UserVisitor;
impl<'de> serde::de::Visitor<'de> for UserVisitor {
type Value = User;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(formatter, "struct User")
}
fn visit_map<V>(self, mut map: V) -> Result<User, V::Error>
where
V: serde::de::MapAccess<'de>,
{
let mut id = None;
let mut username = None;
let mut email = None;
let mut is_admin = None;
while let Some(key) = map.next_key::<String>()? {
match key.as_str() {
"id" => id = Some(map.next_value()?),
"username" => username = Some(map.next_value()?),
"email" => email = Some(map.next_value()?),
"is_admin" => is_admin = Some(map.next_value()?),
_ => return Err(DeError::unknown_field(&key, &["id", "username", "email", "is_admin"])),
}
}
let id = id.ok_or_else(|| DeError::missing_field("id"))?;
let username = username.ok_or_else(|| DeError::missing_field("username"))?;
let email = email.ok_or_else(|| DeError::missing_field("email"))?;
let is_admin = is_admin.ok_or_else(|| DeError::missing_field("is_admin"))?;
Ok(User { id, username, email, is_admin })
}
}
// Use the visitor to deserialize
deserializer.deserialize_struct("User", &["id", "username", "email", "is_admin"], UserVisitor)
}
}Deserialization requires a visitor pattern to handle the incoming data.
use serde::{Serializer, Serialize};
// Serialize a simple wrapper
struct UserId(u64);
impl Serialize for UserId {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
// Serialize as a string instead of a number
serializer.serialize_str(&self.0.to_string())
}
}
// Serialize an enum
enum Status {
Active,
Inactive,
Pending,
}
impl Serialize for Status {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
Status::Active => serializer.serialize_str("active"),
Status::Inactive => serializer.serialize_str("inactive"),
Status::Pending => serializer.serialize_str("pending"),
}
}
}Simple types can serialize directly to primitive values.
use serde::{Deserializer, de::Visitor};
use std::fmt;
struct UserId(u64);
impl<'de> Deserialize<'de> for UserId {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct UserIdVisitor;
impl<'de> Visitor<'de> for UserIdVisitor {
type Value = UserId;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(formatter, "a string containing a u64")
}
fn visit_str<E>(self, v: &str) -> Result<UserId, E>
where
E: serde::de::Error,
{
v.parse::<u64>()
.map(UserId)
.map_err(|_| E::invalid_value(serde::de::Unexpected::Str(v), &self))
}
}
deserializer.deserialize_string(UserIdVisitor)
}
}The visitor handles string input and parses it to the inner type.
use serde::{Serialize, Serializer, ser::SerializeStruct};
// Serialize with computed/derived fields
struct Product {
name: String,
price_cents: u64, // Store as cents, serialize as dollars
}
impl Serialize for Product {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut s = serializer.serialize_struct("Product", 3)?;
s.serialize_field("name", &self.name)?;
s.serialize_field("price", &(self.price_cents as f64 / 100.0))?;
s.serialize_field("price_formatted", &format!("${:.2}", self.price_cents as f64 / 100.0))?;
s.end()
}
}
fn serialize_example() {
let product = Product {
name: "Widget".to_string(),
price_cents: 1999,
};
let json = serde_json::to_string(&product).unwrap();
println!("{}", json);
// {"name":"Widget","price":19.99,"price_formatted":"$19.99"}
}Serialize can transform data during output.
use serde::{Deserialize, Deserializer, de::{Visitor, Error, MapAccess}};
use std::fmt;
#[derive(Debug)]
struct Product {
name: String,
price_cents: u64,
}
impl<'de> Deserialize<'de> for Product {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct ProductVisitor;
impl<'de> Visitor<'de> for ProductVisitor {
type Value = Product;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "a Product object")
}
fn visit_map<V>(self, mut map: V) -> Result<Product, V::Error>
where
V: MapAccess<'de>,
{
let mut name = None;
let mut price_cents = None;
while let Some(key) = map.next_key::<String>()? {
match key.as_str() {
"name" => name = Some(map.next_value()?),
"price" => {
// Accept price as float, convert to cents
let price: f64 = map.next_value()?;
price_cents = Some((price * 100.0) as u64);
}
"price_cents" => price_cents = Some(map.next_value()?),
_ => return Err(Error::unknown_field(&key, &["name", "price", "price_cents"])),
}
}
let name = name.ok_or_else(|| Error::missing_field("name"))?;
let price_cents = price_cents.ok_or_else(|| Error::missing_field("price or price_cents"))?;
Ok(Product { name, price_cents })
}
}
deserializer.deserialize_struct("Product", &["name", "price", "price_cents"], ProductVisitor)
}
}Deserialize can accept multiple input formats and normalize them.
use serde::{Serializer, ser::SerializeTupleVariant};
enum Event {
Click { x: i32, y: i32 },
KeyPress(char),
Resize { width: u32, height: u32 },
}
impl Serialize for Event {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
Event::Click { x, y } => {
// Serialize as ["click", x, y]
let mut tuple = serializer.serialize_tuple_variant("Event", 0, "click", 2)?;
tuple.serialize_field(x)?;
tuple.serialize_field(y)?;
tuple.end()
}
Event::KeyPress(c) => {
// Serialize as ["keyPress", c]
let mut tuple = serializer.serialize_tuple_variant("Event", 1, "keyPress", 1)?;
tuple.serialize_field(c)?;
tuple.end()
}
Event::Resize { width, height } => {
// Serialize as {"resize": {"width": w, "height": h}}
let mut s = serializer.serialize_newtype_variant("Event", 2, "resize", "Resize")?;
let mut inner = s.serialize_struct("Resize", 2)?;
inner.serialize_field("width", width)?;
inner.serialize_field("height", height)?;
inner.end()
}
}
}
}Enums can serialize to various representations depending on format requirements.
use serde::{Deserializer, de::{Visitor, Error, VariantAccess, SeqAccess}};
use std::fmt;
impl<'de> Deserialize<'de> for Event {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct EventVisitor;
impl<'de> Visitor<'de> for EventVisitor {
type Value = Event;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "an Event")
}
fn visit_seq<V>(self, mut seq: V) -> Result<Event, V::Error>
where
V: SeqAccess<'de>,
{
// Expect ["eventName", ...fields]
let event_type: String = seq.next_element()?
.ok_or_else(|| Error::missing_field("event type"))?;
match event_type.as_str() {
"click" => {
let x = seq.next_element()?
.ok_or_else(|| Error::missing_field("x"))?;
let y = seq.next_element()?
.ok_or_else(|| Error::missing_field("y"))?;
Ok(Event::Click { x, y })
}
"keyPress" => {
let c = seq.next_element()?
.ok_or_else(|| Error::missing_field("char"))?;
Ok(Event::KeyPress(c))
}
"resize" => {
let width = seq.next_element()?
.ok_or_else(|| Error::missing_field("width"))?;
let height = seq.next_element()?
.ok_or_else(|| Error::missing_field("height"))?;
Ok(Event::Resize { width, height })
}
_ => Err(Error::unknown_variant(&event_type, &["click", "keyPress", "resize"]))
}
}
}
deserializer.deserialize_seq(EventVisitor)
}
}
enum Event {
Click { x: i32, y: i32 },
KeyPress(char),
Resize { width: u32, height: u32 },
}Enum deserialization typically handles multiple input formats.
use serde::ser::{Serializer, SerializeStruct, SerializeTuple, Error};
// A simple serializer that outputs to a string
struct StringSerializer {
output: String,
}
impl StringSerializer {
fn new() -> Self {
StringSerializer { output: String::new() }
}
}
impl Serializer for StringSerializer {
type Ok = String;
type Error = serde::ser::Error;
type SerializeStruct = StructSerializer;
type SerializeTuple = TupleSerializer;
// ... other associated types
fn serialize_bool(self, v: bool) -> Result<Self::Ok, Self::Error> {
Ok(v.to_string())
}
fn serialize_i64(self, v: i64) -> Result<Self::Ok, Self::Error> {
Ok(v.to_string())
}
fn serialize_u64(self, v: u64) -> Result<Self::Ok, Self::Error> {
Ok(v.to_string())
}
fn serialize_str(self, v: &str) -> Result<Self::Ok, Self::Error> {
Ok(format!("\"{}\"", v))
}
fn serialize_struct(
self,
_name: &'static str,
_len: usize,
) -> Result<Self::SerializeStruct, Self::Error> {
Ok(StructSerializer { output: String::new() })
}
fn serialize_tuple(self, _len: usize) -> Result<Self::SerializeTuple, Self::Error> {
Ok(TupleSerializer { elements: Vec::new() })
}
// Simplified for example - real serializer needs all methods
type SerializeSeq = serde::ser::Impossible<String, Self::Error>;
type SerializeTupleStruct = serde::ser::Impossible<String, Self::Error>;
type SerializeTupleVariant = serde::ser::Impossible<String, Self::Error>;
type SerializeMap = serde::ser::Impossible<String, Self::Error>;
type SerializeStructVariant = serde::ser::Impossible<String, Self::Error>;
fn serialize_i8(self, v: i8) -> Result<Self::Ok, Self::Error> { self.serialize_i64(v as i64) }
fn serialize_i16(self, v: i16) -> Result<Self::Ok, Self::Error> { self.serialize_i64(v as i64) }
fn serialize_i32(self, v: i32) -> Result<Self::Ok, Self::Error> { self.serialize_i64(v as i64) }
fn serialize_u8(self, v: u8) -> Result<Self::Ok, Self::Error> { self.serialize_u64(v as u64) }
fn serialize_u16(self, v: u16) -> Result<Self::Ok, Self::Error> { self.serialize_u64(v as u64) }
fn serialize_u32(self, v: u32) -> Result<Self::Ok, Self::Error> { self.serialize_u64(v as u64) }
fn serialize_f32(self, v: f32) -> Result<Self::Ok, Self::Error> { Ok(v.to_string()) }
fn serialize_f64(self, v: f64) -> Result<Self::Ok, Self::Error> { Ok(v.to_string()) }
fn serialize_char(self, v: char) -> Result<Self::Ok, Self::Error> { Ok(format!("'{}'", v)) }
fn serialize_bytes(self, v: &[u8]) -> Result<Self::Ok, Self::Error> {
Ok(format!("{:?}", v))
}
fn serialize_none(self) -> Result<Self::Ok, Self::Error> { Ok("null".to_string()) }
fn serialize_some<T>(self, value: &T) -> Result<Self::Ok, Self::Error>
where
T: ?Sized + Serialize,
{
value.serialize(self)
}
fn serialize_unit(self) -> Result<Self::Ok, Self::Error> { Ok("()".to_string()) }
fn serialize_unit_struct(self, name: &'static str) -> Result<Self::Ok, Self::Error> {
Ok(name.to_string())
}
fn serialize_unit_variant(
self,
_name: &'static str,
_variant_index: u32,
variant_name: &'static str,
) -> Result<Self::Ok, Self::Error> {
Ok(variant_name.to_string())
}
fn serialize_newtype_struct<T>(
self,
_name: &'static str,
value: &T,
) -> Result<Self::Ok, Self::Error>
where
T: ?Sized + Serialize,
{
value.serialize(self)
}
fn serialize_newtype_variant<T>(
self,
_name: &'static str,
_variant_index: u32,
variant_name: &'static str,
value: &T,
) -> Result<Self::Ok, Self::Error>
where
T: ?Sized + Serialize,
{
Ok(format!("{}({})", variant_name, value.serialize(Self::new())?))
}
fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> {
Err(Self::Error::custom("seq not supported"))
}
fn serialize_tuple_struct(self, _name: &'static str, _len: usize) -> Result<Self::SerializeTupleStruct, Self::Error> {
Err(Self::Error::custom("tuple struct not supported"))
}
fn serialize_tuple_variant(
self,
_name: &'static str,
_variant_index: u32,
_variant_name: &'static str,
_len: usize,
) -> Result<Self::SerializeTupleVariant, Self::Error> {
Err(Self::Error::custom("tuple variant not supported"))
}
fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap, Self::Error> {
Err(Self::Error::custom("map not supported"))
}
fn serialize_struct_variant(
self,
_name: &'static str,
_variant_index: u32,
_variant_name: &'static str,
_len: usize,
) -> Result<Self::SerializeStructVariant, Self::Error> {
Err(Self::Error::custom("struct variant not supported"))
}
}
struct StructSerializer {
output: String,
}
impl SerializeStruct for StructSerializer {
type Ok = String;
type Error = serde::ser::Error;
fn serialize_field<T: ?Sized + Serialize>(
&mut self,
key: &'static str,
value: &T,
) -> Result<(), Self::Error> {
if !self.output.is_empty() {
self.output.push_str(", ");
}
self.output.push_str(key);
self.output.push_str(": ");
self.output.push_str(&value.serialize(StringSerializer::new())?);
Ok(())
}
fn end(self) -> Result<Self::Ok, Self::Error> {
Ok(format!("{{{}}}", self.output))
}
}
struct TupleSerializer {
elements: Vec<String>,
}
impl SerializeTuple for TupleSerializer {
type Ok = String;
type Error = serde::ser::Error;
fn serialize_element<T: ?Sized + Serialize>(&mut self, value: &T) -> Result<(), Self::Error> {
self.elements.push(value.serialize(StringSerializer::new())?);
Ok(())
}
fn end(self) -> Result<Self::Ok, Self::Error> {
Ok(format!("({})", self.elements.join(", ")))
}
}Implementing a full serializer requires handling all possible data shapes.
use serde::de::{Visitor, Error};
// A visitor that can handle multiple input types
struct FlexibleIntVisitor;
impl<'de> Visitor<'de> for FlexibleIntVisitor {
type Value = u64;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "an integer or a string containing an integer")
}
// Handle integer directly
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
where
E: Error,
{
Ok(v)
}
fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
where
E: Error,
{
if v >= 0 {
Ok(v as u64)
} else {
Err(E::custom("expected positive integer"))
}
}
// Handle string containing integer
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: Error,
{
v.parse::<u64>()
.map_err(|_| E::invalid_value(serde::de::Unexpected::Str(v), &self))
}
}
// Using the visitor
fn deserialize_flexible_int<'de, D>(deserializer: D) -> Result<u64, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_any(FlexibleIntVisitor)
}Visitors can accept multiple input formats for the same data.
use serde::{Serialize, Deserialize};
// Combine custom serialization with serde attributes
#[derive(Serialize, Deserialize)]
struct Config {
name: String,
#[serde(serialize_with = "serialize_version")]
#[serde(deserialize_with = "deserialize_version")]
version: Version,
#[serde(skip_serializing_if = "Option::is_none")]
description: Option<String>,
}
struct Version {
major: u32,
minor: u32,
patch: u32,
}
fn serialize_version<S>(v: &Version, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&format!("{}.{}.{}", v.major, v.minor, v.patch))
}
fn deserialize_version<'de, D>(deserializer: D) -> Result<Version, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
let parts: Vec<&str> = s.split('.').collect();
if parts.len() != 3 {
return Err(serde::de::Error::custom("invalid version format"));
}
Ok(Version {
major: parts[0].parse().map_err(serde::de::Error::custom)?,
minor: parts[1].parse().map_err(serde::de::Error::custom)?,
patch: parts[2].parse().map_err(serde::de::Error::custom)?,
})
}Custom serialization can be combined with derive macros via attributes.
use serde::{Serializer, Deserialize, Deserializer, de::Visitor};
use std::fmt;
use std::net::IpAddr;
// Serialize IpAddr in a custom format
fn serialize_ip<S>(ip: &IpAddr, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
// Could serialize with extra info or in a specific format
serializer.serialize_str(&ip.to_string())
}
fn deserialize_ip<'de, D>(deserializer: D) -> Result<IpAddr, D::Error>
where
D: Deserializer<'de>,
{
struct IpAddrVisitor;
impl<'de> Visitor<'de> for IpAddrVisitor {
type Value = IpAddr;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "an IP address string")
}
fn visit_str<E>(self, v: &str) -> Result<IpAddr, E>
where
E: serde::de::Error,
{
v.parse::<IpAddr>()
.map_err(|_| E::invalid_value(serde::de::Unexpected::Str(v), &self))
}
}
deserializer.deserialize_str(IpAddrVisitor)
}
// Usage in a struct
#[derive(serde::Serialize, serde::Deserialize)]
struct Server {
#[serde(serialize_with = "serialize_ip")]
#[serde(deserialize_with = "deserialize_ip")]
bind_address: IpAddr,
port: u16,
}Custom serialization works around orphan rules for external types.
Custom serialization with Serializer and Deserializer traits provides complete control:
Key patterns:
| Task | Approach |
|------|----------|
| Serialize a type | Implement Serialize trait |
| Deserialize a type | Implement Deserialize with a Visitor |
| Serialize one field | Use serialize_with attribute |
| Deserialize one field | Use deserialize_with attribute |
| Accept multiple formats | Visitor with multiple visit_* methods |
| Custom format | Implement full Serializer/Deserializer |
Visitor methods:
| Method | Handles |
|--------|---------|
| visit_bool | true/false |
| visit_i64, visit_u64 | Numbers |
| visit_str | String values |
| visit_seq | Array/sequence |
| visit_map | Object/map |
| visit_none, visit_some | Optional values |
When to implement custom serialization:
Key insight: The Serializer trait is about producing output while Deserializer is about consuming input. Serialization is straightforward—write methods that call the serializer with your data. Deserialization requires the visitor pattern because the deserializer drives the process, asking the visitor what to do with each piece of data it encounters.