Implementing Serialize

The Serialize trait looks like this:

pub trait Serialize {
    fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
        where S: Serializer;

This method's job is to take your type (&self) and turn it into a series of method calls on the given Serializer.

In most cases Serde's codegen is able to generate an appropriate implementation of Serialize for structs and enums defined in your crate. Should you need to customize the serialization behavior for a type in a way that codegen does not support, you can implement Serialize yourself.

Serializing a primitive

As the simplest example, here is the builtin Serialize impl for the primitive i32.

impl Serialize for i32 {
    fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
        where S: Serializer

Serde provides such impls for all of Rust's primitive types so you are not responsible for implementing them yourself, but serialize_i32 and similar methods may be useful if you have a type that needs to be represented as a primitive in its serialized form. For example you could serialize a C-like enum as a primitive number.

Serializing a sequence or map

Complex types follow a three-step process of init, elements, end. The Serializer gets to keep track of some state which the Serialize implementation is responsible for passing to each method.

impl<T> Serialize for Vec<T>
    where T: Serialize
    fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
        where S: Serializer
        let mut state = serializer.serialize_seq(Some(self.len()))?;
        for e in self {
            serializer.serialize_seq_elt(&mut state, e)?;

Serializing a map works the same way but with separate calls in the loop for serialize_map_key and serialize_map_value.

Serializing a struct

Serde distinguishes between four types of structs. Ordinary structs and tuple structs follow the three-step process of init, elements, end just like a sequence or map. Newtype structs and unit structs are more like primitives.

Data formats are encouraged to treat newtype structs as insignificant wrappers around the inner value, serializing just the inner value. See for example JSON's treatment of newtype structs.

// An ordinary struct. Use three-step process:
//   1. serialize_struct
//   2. serialize_struct_elt
//   3. serialize_struct_end
struct Color {
    r: u8,
    g: u8,
    b: u8,

// A tuple struct. Use three-step process:
//   1. serialize_tuple_struct
//   2. serialize_tuple_struct_elt
//   3. serialize_tuple_struct_end
struct Point2D(f64, f64);

// A newtype struct. Use serialize_newtype_struct.
struct Inches(u64);

// A unit struct. Use serialize_unit_struct.
struct Instance;

Serializing an enum

Serializing enum variants is very similar to serializing structs.

enum E {
    // Use three-step process:
    //   1. serialize_struct_variant
    //   2. serialize_struct_variant_elt
    //   3. serialize_struct_variant_end
    Color { r: u8, g: u8, b: u8 },

    // Use three-step process:
    //   1. serialize_tuple_variant
    //   2. serialize_tuple_variant_elt
    //   3. serialize_tuple_variant_end
    Point2D(f64, f64),

    // Use serialize_newtype_variant.

    // Use serialize_unit_variant.

Other special cases

There are three more special cases that are part of the Serializer trait.

There is a method serialize_bytes which serializes a &[u8]. Some formats treat bytes like any other seq, but some formats are able to serialize bytes more compactly. Currently Serde does not use serialize_bytes in the Serialize impl for &[u8] or Vec<u8> but once specialization lands in stable Rust we will begin using it. For now the Bytes and ByteBuf wrappers can be used to wrap &[u8] and Vec<u8> respectively to call serialize_bytes.

There is serialize_seq_fixed_size which is like serialize_seq but for sequences where the length does not need to be serialized because it will be known at deserialization time. The usual example is arrays. In non-self-describing formats a Vec<T> needs to be serialized with its length in order to be able to deserialize a Vec<T> back out. But a [T; 16] can be serialized using serialize_seq_fixed_size because the length will be known at deserialization time without looking at the serialized bytes.

Finally, serialize_some and serialize_none correspond to Option::Some and Option::None. Users tend to have different expectations around the Option enum compared to other enums. Serde JSON will serialize Option::None as null and Option::Some as just the contained value.