Skip to main content

Module serializer

Module serializer 

Source
Expand description

DRF-style serializer layer — #[derive(Serializer)] + serializer::ModelSerializer. Typed JSON output from model instances with field control and validation. DRF-style serializer layer — typed JSON output from model instances.

A serializer is a Rust struct that maps a Model instance to a JSON-ready shape, with per-field control over what is included, renamed, or excluded.

§Quick start

use rustango::Serializer;
use rustango::serializer::ModelSerializer;

#[derive(Serializer, serde::Deserialize, Default)]
#[serializer(model = Post)]
pub struct PostSerializer {
    pub id:         i64,
    pub title:      String,
    #[serializer(read_only)]
    pub created_at: chrono::DateTime<chrono::Utc>,
    #[serializer(write_only)]
    pub secret:     String,
    #[serializer(source = "body")]
    pub content:    String,
    #[serializer(skip)]
    pub tag_ids:    Vec<i64>,   // set manually: s.tag_ids = post.tags_m2m().all(&pool).await?
}

// Serialize:
let s = PostSerializer::from_model(&post);
let json = s.to_value();

// Serialize many:
let json_array = PostSerializer::many_to_value(&posts);

§Field attributes

AttributeEffect on from_modelEffect on JSON outputEffect on writable_fields
(none)mapped from modelincludedyes
read_onlymapped from modelincludedno
write_onlyDefault::default()excludedyes
source = "x"mapped from model.xincludedyes
skipDefault::default()includedno
method = "fn"calls Self::fn(&model)includedno
nestedreads model.<field>.value() then Child::from_model(parent)includedno
nested(strict)same, but panics on unloaded FKincludedno
many = TagSerializerinitializes to Vec::new(); populate via set_<field>(&[Tag]) helperincludedno
slug = "name"clones model.<source>.value()?.name (DRF SlugRelatedField)includedno
validate = "fn"per-field validator called by Self::validate(&self)n/an/a
max_length = Ncaps string length on write (DRF MaxLengthValidator)n/an/a
min_length = Nmin string length on write (DRF MinLengthValidator)n/an/a
min = N / max = Ninclusive integer bounds on write (DRF Min/MaxValueValidator)n/an/a

§Declarative field validators

max_length / min_length / min / max are checked on write (create/update through a ViewSet) and surface DRF-shape 400s. They auto-inherit from the model: every writable field is validated against the model’s crate::core::FieldSchema (max_length, min, max, and choices) even with no attribute; a per-field attribute overrides the inherited value. min_length is serializer-only (no model column). choices is inherited from the model (no attribute). String length is measured in characters. For arbitrary rules, use validate = "fn" (per-field) or the container validate (cross-field).

§Nested serializers — auto-resolved via #[serializer(nested)]

When the field type is another serializer and the model’s FK is already loaded (via select_related), the macro emits a from_model initializer that walks the FK automatically:

#[derive(Serializer, serde::Deserialize, Default)]
#[serializer(model = Post)]
struct PostWithAuthor {
    pub id: i64,
    pub title: String,
    #[serializer(nested)]
    pub author: AuthorSerializer,
}

If the FK was not loaded (no select_related), the field falls back to Default::default() rather than panicking — production degrades gracefully. Use #[serializer(nested(strict))] to opt back into the v0.18.1 panic-on-unloaded behaviour for tests.

For lists of children (one-to-many / M2M), use #[serializer(many = ChildSerializer)]. The macro emits a set_<field>(&[Child]) setter; the caller fetches the children and calls it after from_model (auto-load isn’t possible because the M2M accessor is async).

§Computed fields — #[serializer(method = "fn")]

DRF SerializerMethodField analog. The macro emits a from_model initializer that calls Self::fn(&model):

impl PostSerializer {
    fn excerpt(model: &Post) -> String {
        model.body.chars().take(80).collect::<String>() + "…"
    }
}

#[derive(Serializer, serde::Deserialize, Default)]
#[serializer(model = Post)]
struct PostSerializer {
    pub title: String,
    #[serializer(method = "excerpt")]
    pub excerpt: String,
}

§Validation

Cross-field validation: implement validate(&self) as an inherent method on the serializer struct:

impl PostSerializer {
    pub fn validate(&self) -> Result<(), rustango::forms::FormErrors> {
        let mut errors = rustango::forms::FormErrors::default();
        if self.title.is_empty() {
            errors.add("title", "title cannot be empty");
        }
        if errors.is_empty() { Ok(()) } else { Err(errors) }
    }
}

Per-field validators: declare #[serializer(validate = "fn_name")] on the field and write fn fn_name(value: &T) -> Result<(), String> as an associated method. The macro-generated validate(&self) aggregates per-field results into a FormErrors.

Traits§

ModelSerializer
Core serializer trait. Implemented by #[derive(Serializer)] structs.

Functions§

check_unique_together_pool
Django-shape UniqueTogetherValidator — pre-save check that a candidate row doesn’t collide with an existing row on any of the model’s declared unique_together constraints. Issue #437.
hyperlink_url
Substitute {pk} in template with the formatted PK value.
hyperlinked_to_value
Wrap a serializer’s JSON output with a url field (from the model’s PK) and optional <fk>_url fields (from named FK templates). Issue #434.