Serde Documentation

website·Indexed 53 days ago

https://serde.rs

Comprehensive guide for the Serde Rust framework, covering serialization and deserialization. Documentation includes details on the Serde data model, implementing Serializer and Deserializer traits, custom attribute usage (flatten, rename, skip), enum representations, no-std support, and writing custom data formats.

Tokens
20.9K
Snippets
48
Records
85
Agent score
50%

What's inside Serde

  1. Overview of Serde attributes

    Serde attributes are used to customize the Serialize and Deserialize implementations generated by Serde's derive macros. They require a Rust compiler version 1.15 or newer. Attributes are categorized based on where they are applied:

    • Container attributes: Apply to a struct or enum declaration.
    • Variant attributes: Apply to a specific variant of an enum.
    • Field attributes: Apply to a single field within a struct or an enum variant.

    A single struct, enum, variant, or field can have multiple attributes applied to it.

  2. Overview of implementing the Serde Serializer trait

    To implement a custom serializer in Serde, you must implement the Serializer trait. Each method in the trait corresponds to a specific type in the Serde data model. The implementation's responsibility is to map these data model types into your desired output format (e.g., JSON, XML, or a custom binary format).
  3. Overview of Serde serialization framework

    Serde is a framework for serializing and deserializing Rust data structures efficiently and generically. It works by separating data structures (which implement Serialize and Deserialize traits) from data formats (which handle the actual encoding/decoding). This design avoids runtime reflection by using Rust's trait system, allowing the compiler to optimize the interaction between structures and formats to speeds comparable to handwritten serializers.
  4. Efficiently discard data using IgnoredAny

    The serde::de::IgnoredAny type provides an efficient way to discard data during deserialization. Unlike serde_json::Value, which captures and stores the data in memory, IgnoredAny allows the deserializer to skip over values without allocating memory or storing any information about the data being processed. This is useful when you want to ignore specific fields or elements in a sequence while maintaining high performance.
  5. Transcode between Serde data formats using serde-transcode

    The serde-transcode crate allows you to convert data from an arbitrary Serde Deserializer to an arbitrary Serde Serializer without loading the entire input into an intermediate memory structure. This enables memory-efficient, streaming conversion between any self-describing Serde formats (e.g., JSON to CBOR, or compacting JSON by removing whitespace).
    use std::io;
    
    fn main() {
        // A JSON input with plenty of whitespace.
        let input = r"#
          {
            "a boolean": true,
            "an array": [3, 2, 1]
          }
        "#;
    
        // A JSON deserializer. You can use any Serde Deserializer here.
        let mut deserializer = serde_json::Deserializer::from_str(input);
    
        // A compacted JSON serializer. You can use any Serde Serializer here.
        let mut serializer = serde_json::Serializer::new(io::stdout());
    
        // Prints `{"a boolean":true,"an array":[3,2,1]}` to stdout.
        // This line works with any self-describing Deserializer and any Serializer.
        serde_transcode::transcode(&mut deserializer, &mut serializer).unwrap();
    }
  6. Understand Serde enum representations

    Serde supports four different ways to represent enums during serialization and deserialization. Choosing the right representation depends on your target data format (e.g., JSON), whether you are in a no-std/no-alloc environment, and the structure of your enum variants.

    1. Externally Tagged (Default)

    The variant name is used as a key that wraps the variant's data.

    • JSON Example: {"Request": {"id": "...", "method": "..."}}
    • Characteristics: Works with any variant type (struct, tuple, newtype, unit). It is the only representation that works in no-alloc projects. It allows knowing the variant before parsing the content.

    2. Internally Tagged

    The tag identifying the variant is placed inside the content object alongside other fields.

    • Usage: Use #[serde(tag = "type")].
    • JSON Example: {"type": "Request", "id": "..."}
    • Constraints: Works for struct variants, newtype variants (containing structs/maps), and unit variants. Does not work with tuple variants (will cause a compile-time error).
    • Requirement: Requires the alloc feature enabled.

    3. Adjacently Tagged

    The tag and the content are two separate, adjacent fields within the same object.

    • Usage: Use #[serde(tag = "t", content = "c")].
    • JSON Example: {"t": "Para", "c": [{...}]}
    • Requirement: Requires the alloc feature enabled.

    4. Untagged

    There is no explicit tag. Serde attempts to match the data against each variant in order until one succeeds.

    • Usage: Use #[serde(untagged)].
    • JSON Example: {"id": "...", "method": "..."}
    • Characteristics: Can handle any variant type. Useful for matching data against different types (e.g., an integer vs. an array).
    • Requirement: Requires the alloc feature enabled.
  7. Understand Serde error handling architecture

    Serde error handling is split into two layers:

    1. Format Errors: Errors originating from the Serializer or Deserializer (e.g., syntax errors, unexpected EOF, or type mismatches like expecting a boolean but finding a string). These are specific to the data format (like JSON or YAML).
    2. Data Structure Errors: Errors originating from the Serialize or Deserialize implementations of your data structures (e.g., a poisoned Mutex during serialization or a missing required field during deserialization). These are handled via the ser::Error and de::Error traits.

    To allow data structures to report errors back to the format, the format's error type must implement the ser::Error and de::Error traits. This allows the data structure to call .custom() to create a format-specific error.

  8. Understand the role of Serde when writing a data format

    When implementing a new data format with Serde, it is critical to distinguish between parsing and Serde's core responsibilities. Serde is not a parsing library; it does not provide tools to parse raw input strings or bytes into a structured format. Instead, Serde provides the framework for:

    • Serialization: Taking arbitrary data structures from the user and rendering them into your specific format with maximum efficiency.
    • Deserialization: Interpreting data that you have already parsed into data structures of the user's choice with maximum efficiency.

    You are responsible for writing the parsing logic from scratch or using a separate parsing library to feed data into your Serde Deserializer implementation.

  9. Standard module structure for Serde data format crates

    When implementing a Serde data format crate, it is convention to provide specific types and functions in the root module (or re-exported from the root module) to ensure consistency across the ecosystem.

    Standard root-level exports include:

    • An Error type used for both serialization and deserialization.
    • A Result type alias equivalent to std::result::Result<T, Error>.
    • A Serializer type implementing serde::Serializer.
    • A Deserializer type implementing serde::Deserializer.
    • to_abc functions for serialization (e.g., to_string, to_bytes, to_writer).
    • from_xyz functions for deserialization (e.g., from_str, from_bytes, from_reader).

    If the format provides specialized APIs beyond the standard Serializer and Deserializer traits, these should be exposed under top-level ser and de modules (e.g., serde_json::ser::Formatter).

    mod de;
    mod error;
    mod ser;
    
    pub use de::{from_str, Deserializer};
    pub use error::{Error, Result};
    pub use ser::{to_string, Serializer};
  10. Understand the Serde Data Model architecture

    The Serde data model acts as an intermediate type system that allows Rust data structures and data formats to interact. It decouples the data structure from the specific serialization format.

    Serialization Flow

    1. The Serialize implementation for a data structure maps the structure into the Serde data model by invoking methods on a Serializer.
    2. The Serializer implementation for a specific data format (e.g., JSON, YAML) maps the Serde data model into the final output representation.

    Deserialization Flow

    1. The Deserializer implementation for a data format maps the input data into the Serde data model.
    2. The Deserialize implementation for a data structure receives the data model via a Visitor implementation, which is responsible for mapping the data model types back into the specific Rust data structure.
  11. Manually implement Serialize and Deserialize traits in Serde

    While Serde's #[derive(Serialize, Deserialize)] macro provides default behavior for most structs and enums, you can achieve full control over serialization logic by manually implementing the Serialize and Deserialize traits.

    These traits are generic over the serialization format, which is abstracted by the Serializer and Deserializer traits. This allows your manual implementation to work across different formats (e.g., JSON, Postcard, etc.) as long as they implement the corresponding format traits.

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

    pub trait Deserialize<'de>: Sized { fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de>; }

  12. Understand the Deserialize trait and Deserializer entry points

    The Deserialize<'de> trait is used to map data into the Serde data model. To implement it, you provide a Deserializer with a Visitor that the deserializer will drive to construct your type.

    There are two main ways a Deserializer can be driven:

    1. deserialize_any: Used by self-describing formats (like JSON) that can determine the type from the input data itself. Using this makes your type incompatible with non-self-describing formats like Postcard.
    2. deserialize_* methods: These provide type hints (e.g., deserialize_i32, deserialize_map) to the deserializer. This is required for non-self-describing formats that need to know the expected type in advance.

    Note: A Deserializer might not strictly follow the type hint. For example, a JSON deserializer might call visit_i64 even if you called deserialize_i32 because JSON treats all integers similarly. Always implement multiple compatible visitor methods to ensure robustness.

    pub trait Deserialize<'de>: Sized {
        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
        where
            D: Deserializer<'de>;
    }