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
| Attribute | Effect on from_model | Effect on JSON output | Effect on writable_fields |
|---|---|---|---|
| (none) | mapped from model | included | yes |
read_only | mapped from model | included | no |
write_only | Default::default() | excluded | yes |
source = "x" | mapped from model.x | included | yes |
skip | Default::default() | included | no |
method = "fn" | calls Self::fn(&model) | included | no |
nested | reads model.<field>.value() then Child::from_model(parent) | included | no |
nested(strict) | same, but panics on unloaded FK | included | no |
many = TagSerializer | initializes to Vec::new(); populate via set_<field>(&[Tag]) helper | included | no |
slug = "name" | clones model.<source>.value()?.name (DRF SlugRelatedField) | included | no |
validate = "fn" | per-field validator called by Self::validate(&self) | n/a | n/a |
max_length = N | caps string length on write (DRF MaxLengthValidator) | n/a | n/a |
min_length = N | min string length on write (DRF MinLengthValidator) | n/a | n/a |
min = N / max = N | inclusive integer bounds on write (DRF Min/MaxValueValidator) | n/a | n/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§
- Model
Serializer - 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 declaredunique_togetherconstraints. Issue #437. - hyperlink_
url - Substitute
{pk}intemplatewith the formatted PK value. - hyperlinked_
to_ value - Wrap a serializer’s JSON output with a
urlfield (from the model’s PK) and optional<fk>_urlfields (from named FK templates). Issue #434.