Serialization & Deserialization

Adapters features highly optimized and fully type-safe serialization and deserialization traits. These traits define how memory structures are converted to and from intermediate Value dynamic trees.


The Serialize Trait

The Serialize trait defines how a typed Rust instance maps into a dynamic Value.

#![allow(unused)]
fn main() {
pub trait Serialize {
    fn serialize(&self) -> Value;
}
}

Every standard primitive type implements this out of the box:

  • Booleans (bool)
  • All integers (i8, i16, i32, i64, u8, u16, u32, u64, usize)
  • All floats (f32, f64)
  • Text types (String, &str)
  • Container types (Option<T>, Vec<T>, BTreeMap<String, T>)

The Deserialize Trait

The Deserialize trait is the inverse operation, decoding a Value tree back into a typed Rust instance while auditing numeric bounds and ranges:

#![allow(unused)]
fn main() {
pub trait Deserialize: Sized {
    fn deserialize(value: Value) -> Result<Self, Error>;
}
}

Safety and Strict Numeric Bounds

Unlike naive decoders that might cause silent overflows, Adapters performs type-safe checks during deserialization:

  • If you deserialize a value of 300 into a u8 field, it will return a clean DeserializationError explaining that 300 overflows the bounds of u8.
  • If an unsigned integer type (e.g. u32) receives a negative value (e.g. -10), it is caught and rejected immediately.

Custom Manual Implementations

While using the #[derive(Schema)] macro covers 99% of use cases, you can implement these traits manually for total control:

#![allow(unused)]
fn main() {
use adapters::{Serialize, Deserialize, Value, Error, error::DeserializationError};

struct Point {
    x: i32,
    y: i32,
}

impl Serialize for Point {
    fn serialize(&self) -> Value {
        let mut map = std::collections::BTreeMap::new();
        map.insert("x".to_string(), Value::Int(self.x as i64));
        map.insert("y".to_string(), Value::Int(self.y as i64));
        Value::Object(map)
    }
}

impl Deserialize for Point {
    fn deserialize(value: Value) -> Result<Self, Error> {
        let obj = value.as_object().ok_or_else(|| {
            DeserializationError::new("expected an object representation")
        })?;
        
        let x = obj.get("x")
            .and_then(|v| v.as_int())
            .ok_or_else(|| DeserializationError::new("missing coordinate x"))? as i32;
            
        let y = obj.get("y")
            .and_then(|v| v.as_int())
            .ok_or_else(|| DeserializationError::new("missing coordinate y"))? as i32;

        Ok(Point { x, y })
    }
}
}

Nested/Recursive Serialization & Deserialization

Adapters fully supports deep nested models under both programmatic use and standard macro derivation.

When you define a structure that references another structure as a field (both having derived Schema), the serialization and deserialization routines operate recursively:

  1. Nested Serialization: Translates the complex model tree from the nested structures all the way down into deep hierarchical key-value JSON trees (Value::Object).
  2. Nested Deserialization: Reconstructs custom nested Rust models dynamically from the intermediate hierarchy, handling all type verification and bounds checking nested deep inside the child structures.
#![allow(unused)]
fn main() {
use adapters::prelude::*;

#[derive(Schema, Debug)]
struct Metadata {
    created_at: String,
    version: String,
}

#[derive(Schema, Debug)]
struct Post {
    title: String,
    metadata: Metadata, // Automatically maps, validates, and serializes recursively!
}
}