Loading pageā¦
Rust walkthroughs
Loading pageā¦
serde::de::DeserializeSeed enable context-aware deserialization patterns?DeserializeSeed separates the deserialization logic from the type being deserialized, allowing external context to flow into the deserialization process. While Deserialize is implemented on types that can deserialize themselves independently, DeserializeSeed implementations carry additional stateāenabling patterns like resolving references to existing objects, validating against external constraints, or injecting configuration. The seed's deserialize method receives both the deserializer and the seed's internal context, bridging the gap between standalone deserialization and context-dependent reconstruction.
use serde::Deserialize;
// Standard Deserialize works for self-contained data
#[derive(Deserialize, Debug)]
struct User {
id: u64,
name: String,
email: String,
}
fn main() {
let json = r#"{"id": 1, "name": "Alice", "email": "alice@example.com"}"#;
let user: User = serde_json::from_str(json).unwrap();
println!("{:?}", user);
}Standard Deserialize handles data that exists entirely within the serialized form.
use serde::Deserialize;
// Problem: What if we need external context during deserialization?
// Example: User references a department by ID, but we need the actual Department object
#[derive(Debug, Clone)]
struct Department {
id: u64,
name: String,
}
#[derive(Debug)]
struct User {
id: u64,
name: String,
department: Department, // We want the actual object, not just ID
}
// The serialized form has just the department ID:
// {"id": 1, "name": "Alice", "department_id": 42}
// But we need the Department from our registry:
// How do we pass the registry into deserialization?
fn main() {
let registry = vec![
Department { id: 42, name: "Engineering".into() },
Department { id: 43, name: "Marketing".into() },
];
// Problem: serde_json::from_str can't access `registry`
// Deserialize trait has no way to receive external context
}This is where DeserializeSeed becomes essential.
use serde::de::{DeserializeSeed, Deserializer, Error, Visitor};
use std::collections::HashMap;
// The seed carries context needed for deserialization
struct UserSeed<'a> {
departments: &'a HashMap<u64, Department>,
}
// DeserializeSeed allows the seed to drive deserialization
impl<'de, 'a> DeserializeSeed<'de> for UserSeed<'a> {
// The type we're producing
type Value = User;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
// The seed controls how deserialization proceeds
// It can use its context during the process
struct UserVisitor<'a> {
departments: &'a HashMap<u64, Department>,
}
impl<'de, 'a> Visitor<'de> for UserVisitor<'a> {
type Value = User;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a user object")
}
fn visit_map<M>(self, mut map: M) -> Result<User, M::Error>
where
M: serde::de::MapAccess<'de>,
{
let mut id = None;
let mut name = None;
let mut department_id = None;
while let Some(key) = map.next_key::<String>()? {
match key.as_str() {
"id" => id = Some(map.next_value()?),
"name" => name = Some(map.next_value()?),
"department_id" => department_id = Some(map.next_value()?),
_ => { map.next_value::<serde::de::IgnoredAny>()?; }
}
}
let id = id.ok_or_else(|| Error::missing_field("id"))?;
let name = name.ok_or_else(|| Error::missing_field("name"))?;
let department_id = department_id.ok_or_else(|| Error::missing_field("department_id"))?;
// Use context to resolve department
let department = self.departments.get(&department_id)
.cloned()
.ok_or_else(|| Error::custom("unknown department id"))?;
Ok(User { id, name, department })
}
}
deserializer.deserialize_map(UserVisitor {
departments: self.departments,
})
}
}
#[derive(Debug, Clone)]
struct Department {
id: u64,
name: String,
}
#[derive(Debug)]
struct User {
id: u64,
name: String,
department: Department,
}
fn main() {
let mut departments = HashMap::new();
departments.insert(42, Department { id: 42, name: "Engineering".into() });
departments.insert(43, Department { id: 43, name: "Marketing".into() });
let json = r#"{"id": 1, "name": "Alice", "department_id": 42}"#;
// Use the seed with context
let seed = UserSeed { departments: &departments };
let user: User = seed.deserialize(&mut serde_json::Deserializer::from_str(json)).unwrap();
println!("{:?}", user);
// User { id: 1, name: "Alice", department: Department { id: 42, name: "Engineering" } }
}The seed carries the department registry and uses it during deserialization.
use serde::de::{DeserializeSeed, Deserializer};
use serde::Deserialize;
// Standard Deserialize: Type knows how to deserialize itself
impl<'de> Deserialize<'de> for User {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
// No external context available
// Can only use what's in the serialized data
todo!()
}
}
// DeserializeSeed: Seed knows how to deserialize the type
impl<'de, 'a> DeserializeSeed<'de> for UserSeed<'a> {
type Value = User;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
// Has access to self.departments
// Can use external context during deserialization
todo!()
}
}
// Key difference:
// - Deserialize: impl on the TYPE
// - DeserializeSeed: impl on a SEED type that carries context
// This means:
// - Deserialize: One impl per type, no context
// - DeserializeSeed: Multiple seeds possible, each with different contextDeserializeSeed implementations are separate from the target type.
use serde::de::{DeserializeSeed, Deserializer, Error, Visitor};
use std::collections::HashMap;
struct Document {
id: u64,
title: String,
author: User,
}
struct User {
id: u64,
name: String,
}
// Seed for strict validation context
struct StrictDocumentSeed<'a> {
users: &'a HashMap<u64, User>,
}
// Seed for lenient validation context
struct LenientDocumentSeed<'a> {
users: &'a HashMap<u64, User>,
}
impl<'de, 'a> DeserializeSeed<'de> for StrictDocumentSeed<'a> {
type Value = Document;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
// Strict: Author must exist, all fields required
// ... implementation validates strictly
unimplemented!()
}
}
impl<'de, 'a> DeserializeSeed<'de> for LenientDocumentSeed<'a> {
type Value = Document;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
// Lenient: Unknown author becomes placeholder, missing fields get defaults
// ... implementation is forgiving
unimplemented!()
}
}
fn main() {
let users = HashMap::new();
// Same JSON, different validation behavior
let json = r#"{"id": 1, "title": "Test", "author_id": 999}"#;
// Strict seed: fails on unknown author
let strict_seed = StrictDocumentSeed { users: &users };
// Lenient seed: creates placeholder author
let lenient_seed = LenientDocumentSeed { users: &users };
}Different seeds can provide different deserialization behaviors for the same type.
use serde::de::{DeserializeSeed, Deserializer, Error, Visitor};
use std::collections::HashMap;
#[derive(Debug, Clone)]
struct Entity {
id: u64,
name: String,
parent_id: Option<u64>, // References another entity
}
struct EntitySeed<'a> {
registry: &'a HashMap<u64, Entity>,
}
impl<'de, 'a> DeserializeSeed<'de> for EntitySeed<'a> {
type Value = Entity;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
struct EntityVisitor<'a> {
registry: &'a HashMap<u64, Entity>,
}
impl<'de, 'a> Visitor<'de> for EntityVisitor<'a> {
type Value = Entity;
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.write_str("an entity object")
}
fn visit_map<M>(self, mut map: M) -> Result<Entity, M::Error>
where
M: serde::de::MapAccess<'de>,
{
let mut id = None;
let mut name = None;
let mut parent_id = None;
while let Some(key) = map.next_key::<String>()? {
match key.as_str() {
"id" => id = Some(map.next_value()?),
"name" => name = Some(map.next_value()?),
"parent_id" => parent_id = Some(map.next_value()?),
_ => { map.next_value::<serde::de::IgnoredAny>()?; }
}
}
// Validate parent_id references an existing entity
if let Some(pid) = parent_id {
if !self.registry.contains_key(&pid) {
return Err(Error::custom(format!(
"parent_id {} does not reference known entity",
pid
)));
}
}
Ok(Entity {
id: id.ok_or_else(|| Error::missing_field("id"))?,
name: name.ok_or_else(|| Error::missing_field("name"))?,
parent_id,
})
}
}
deserializer.deserialize_map(EntityVisitor {
registry: self.registry,
})
}
}
fn main() {
let mut registry = HashMap::new();
registry.insert(1, Entity { id: 1, name: "Root".into(), parent_id: None });
// Deserialize with validation against registry
let json = r#"{"id": 2, "name": "Child", "parent_id": 1}"#;
let seed = EntitySeed { registry: ®istry };
let entity: Entity = seed.deserialize(&mut serde_json::Deserializer::from_str(json)).unwrap();
println!("{:?}", entity);
// This would fail - parent_id references unknown entity
let json = r#"{"id": 3, "name": "Orphan", "parent_id": 999}"#;
let result = seed.deserialize(&mut serde_json::Deserializer::from_str(json));
assert!(result.is_err());
}The registry validates references during deserialization.
use serde::de::{DeserializeSeed, Deserializer, SeqAccess, Visitor};
use std::collections::HashMap;
// When deserializing collections, each element needs the seed
struct UserListSeed<'a> {
departments: &'a HashMap<u64, Department>,
}
impl<'de, 'a> DeserializeSeed<'de> for UserListSeed<'a> {
type Value = Vec<User>;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
struct UserListVisitor<'a> {
departments: &'a HashMap<u64, Department>,
}
impl<'de, 'a> Visitor<'de> for UserListVisitor<'a> {
type Value = Vec<User>;
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.write_str("a list of users")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Vec<User>, A::Error>
where
A: SeqAccess<'de>,
{
let mut users = Vec::new();
// Use UserSeed for each element
let user_seed = UserSeed { departments: self.departments };
while let Some(user) = seq.next_element_seed(user_seed.clone())? {
users.push(user);
}
Ok(users)
}
}
// Note: Need to make UserSeed Clone for next_element_seed
deserializer.deserialize_seq(UserListVisitor {
departments: self.departments,
})
}
}
#[derive(Clone)]
struct Department {
id: u64,
name: String,
}
#[derive(Debug)]
struct User {
id: u64,
name: String,
department: Department,
}
#[derive(Clone)]
struct UserSeed<'a> {
departments: &'a HashMap<u64, Department>,
}
impl<'de, 'a> DeserializeSeed<'de> for UserSeed<'a> {
type Value = User;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where D: Deserializer<'de> {
unimplemented!()
}
}Collections use seeds for each element to maintain context throughout.
use serde::de::{DeserializeSeed, Deserializer, Error, Visitor};
// Deserialization can be configured via seed
struct Config {
max_name_length: usize,
allow_unknown_fields: bool,
}
struct ConfiguredUserSeed {
config: Config,
}
impl<'de> DeserializeSeed<'de> for ConfiguredUserSeed {
type Value = User;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
struct ConfiguredUserVisitor {
config: Config,
}
impl<'de> Visitor<'de> for ConfiguredUserVisitor {
type Value = User;
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.write_str("a user object")
}
fn visit_map<M>(self, mut map: M) -> Result<User, M::Error>
where
M: serde::de::MapAccess<'de>,
{
let mut id = None;
let mut name = None;
while let Some(key) = map.next_key::<String>()? {
match key.as_str() {
"id" => id = Some(map.next_value()?),
"name" => {
let n: String = map.next_value()?;
// Apply configuration
if n.len() > self.config.max_name_length {
return Err(Error::custom(format!(
"name exceeds max length of {}",
self.config.max_name_length
)));
}
name = Some(n);
}
_ => {
if !self.config.allow_unknown_fields {
return Err(Error::unknown_field(&key, &["id", "name"]));
}
map.next_value::<serde::de::IgnoredAny>()?;
}
}
}
Ok(User {
id: id.ok_or_else(|| Error::missing_field("id"))?,
name: name.ok_or_else(|| Error::missing_field("name"))?,
})
}
}
deserializer.deserialize_map(ConfiguredUserVisitor {
config: self.config,
})
}
}
struct User {
id: u64,
name: String,
}
fn main() {
// Strict configuration
let strict_config = Config {
max_name_length: 10,
allow_unknown_fields: false,
};
// Lenient configuration
let lenient_config = Config {
max_name_length: 1000,
allow_unknown_fields: true,
};
let json = r#"{"id": 1, "name": "VeryLongNameThatExceedsLimit", "extra": "field"}"#;
// With strict config: name too long AND unknown field
let strict_seed = ConfiguredUserSeed { config: strict_config };
let result = strict_seed.deserialize(&mut serde_json::Deserializer::from_str(json));
assert!(result.is_err());
// With lenient config: would succeed (if name was shorter)
}Configuration controls deserialization behavior through the seed.
use serde::de::{DeserializeSeed, Deserializer};
use std::collections::HashMap;
use std::marker::PhantomData;
// Factory creates seeds with consistent context
struct SeedFactory<'a> {
users: &'a HashMap<u64, User>,
posts: &'a HashMap<u64, Post>,
}
impl<'a> SeedFactory<'a> {
fn user_seed(&self) -> UserSeed<'a> {
UserSeed { users: self.users }
}
fn post_seed(&self) -> PostSeed<'a> {
PostSeed {
users: self.users,
posts: self.posts,
}
}
fn comment_seed(&self) -> CommentSeed<'a> {
CommentSeed {
users: self.users,
posts: self.posts,
}
}
}
struct UserSeed<'a> {
users: &'a HashMap<u64, User>,
}
struct PostSeed<'a> {
users: &'a HashMap<u64, User>,
posts: &'a HashMap<u64, Post>,
}
struct CommentSeed<'a> {
users: &'a HashMap<u64, User>,
posts: &'a HashMap<u64, Post>,
}
struct User { id: u64, name: String }
struct Post { id: u64, author_id: u64, title: String }
struct Comment { id: u64, post_id: u64, author_id: u64, content: String }
// Implement DeserializeSeed for each seed...
fn main() {
let mut users = HashMap::new();
let mut posts = HashMap::new();
users.insert(1, User { id: 1, name: "Alice".into() });
posts.insert(1, Post { id: 1, author_id: 1, title: "Hello".into() });
let factory = SeedFactory { users: &users, posts: &posts };
// Consistent context across all deserializations
let user_seed = factory.user_seed();
let post_seed = factory.post_seed();
}A factory ensures all seeds share the same context.
use serde::de::{DeserializeSeed, Deserializer, Visitor};
use serde::Deserialize;
// Types can implement both Deserialize and support seeds
#[derive(Debug, Deserialize)]
struct User {
id: u64,
name: String,
// email is optional with Deserialize
email: Option<String>,
}
// A seed that requires email
struct StrictUserSeed;
impl<'de> DeserializeSeed<'de> for StrictUserSeed {
type Value = User;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
struct StrictUserVisitor;
impl<'de> Visitor<'de> for StrictUserVisitor {
type Value = User;
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.write_str("a user object with required email")
}
fn visit_map<M>(self, mut map: M) -> Result<User, M::Error>
where
M: serde::de::MapAccess<'de>,
{
// Use the standard Deserialize logic, then validate
let user: User = map.next_value()?; // Uses User's Deserialize
// Additional validation from seed
if user.email.is_none() {
return Err(serde::de::Error::custom("email is required"));
}
Ok(user)
}
}
deserializer.deserialize_map(StrictUserVisitor)
}
}
fn main() {
// Standard Deserialize: email is optional
let json = r#"{"id": 1, "name": "Alice"}"#;
let user: User = serde_json::from_str(json).unwrap();
println!("User (no email): {:?}", user);
// With seed: email is required
let result = StrictUserSeed.deserialize(&mut serde_json::Deserializer::from_str(json));
assert!(result.is_err());
}Seeds can add validation on top of standard deserialization.
use serde::de::{DeserializeSeed, Deserializer, Error, Visitor};
use std::collections::HashMap;
// Real-world pattern: resolving database IDs to actual objects
#[derive(Debug)]
struct Order {
id: u64,
customer: Customer, // Resolved from customer_id
items: Vec<OrderItem>, // Resolved from item_ids
}
#[derive(Debug, Clone)]
struct Customer {
id: u64,
name: String,
email: String,
}
#[derive(Debug, Clone)]
struct Product {
id: u64,
name: String,
price: f64,
}
#[derive(Debug)]
struct OrderItem {
product: Product,
quantity: u32,
}
struct OrderSeed<'a> {
customers: &'a HashMap<u64, Customer>,
products: &'a HashMap<u64, Product>,
}
impl<'de, 'a> DeserializeSeed<'de> for OrderSeed<'a> {
type Value = Order;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
struct OrderVisitor<'a> {
customers: &'a HashMap<u64, Customer>,
products: &'a HashMap<u64, Product>,
}
impl<'de, 'a> Visitor<'de> for OrderVisitor<'a> {
type Value = Order;
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.write_str("an order object")
}
fn visit_map<M>(self, mut map: M) -> Result<Order, M::Error>
where
M: serde::de::MapAccess<'de>,
{
let mut id = None;
let mut customer_id = None;
let mut items = Vec::new();
while let Some(key) = map.next_key::<String>()? {
match key.as_str() {
"id" => id = Some(map.next_value()?),
"customer_id" => customer_id = Some(map.next_value()?),
"items" => {
// Deserialize items with product lookup
while let Some(item_data) = map.next_value::<Option<serde_json::Value>>()? {
if let Some(item_json) = item_data {
let product_id: u64 = serde_json::from_value(
item_json.get("product_id").cloned().unwrap()
).map_err(Error::custom)?;
let quantity: u32 = serde_json::from_value(
item_json.get("quantity").cloned().unwrap()
).map_err(Error::custom)?;
let product = self.products.get(&product_id)
.cloned()
.ok_or_else(|| Error::custom("unknown product"))?;
items.push(OrderItem { product, quantity });
}
}
}
_ => { map.next_value::<serde::de::IgnoredAny>()?; }
}
}
let id = id.ok_or_else(|| Error::missing_field("id"))?;
let customer_id = customer_id.ok_or_else(|| Error::missing_field("customer_id"))?;
let customer = self.customers.get(&customer_id)
.cloned()
.ok_or_else(|| Error::custom("unknown customer"))?;
Ok(Order { id, customer, items })
}
}
deserializer.deserialize_map(OrderVisitor {
customers: self.customers,
products: self.products,
})
}
}
fn main() {
let mut customers = HashMap::new();
customers.insert(1, Customer { id: 1, name: "Alice".into(), email: "alice@example.com".into() });
let mut products = HashMap::new();
products.insert(101, Product { id: 101, name: "Widget".into(), price: 9.99 });
products.insert(102, Product { id: 102, name: "Gadget".into(), price: 19.99 });
let json = r#"{
"id": 1001,
"customer_id": 1,
"items": [
{"product_id": 101, "quantity": 2},
{"product_id": 102, "quantity": 1}
]
}"#;
let seed = OrderSeed { customers: &customers, products: &products };
let order: Order = seed.deserialize(&mut serde_json::Deserializer::from_str(json)).unwrap();
println!("Order {} for {}", order.id, order.customer.name);
for item in &order.items {
println!(" {} x {} @ ${:.2}", item.quantity, item.product.name, item.product.price);
}
}This pattern resolves database references during deserialization.
Key concepts:
| Aspect | Deserialize | DeserializeSeed |
|--------|---------------|-------------------|
| Implemented on | Target type | Seed type |
| Context | None available | Carried in seed |
| Use case | Self-contained data | Context-dependent data |
| Flexibility | One impl per type | Multiple seeds possible |
When to use DeserializeSeed:
Implementation pattern:
DeserializeSeed with the desired Value typeVisitor that accesses the seed's contextseed.deserialize(deserializer) instead of Type::deserializeKey insight: DeserializeSeed separates the "how" of deserialization from the "what" by implementing the deserialization logic on a separate seed type that carries context. This enables patterns impossible with Deserialize alone: resolving foreign key references during deserialization, applying configuration-dependent validation, and supporting multiple deserialization behaviors for the same type. The seed acts as a factory that provides both the visitor implementation and the context it needs, making context-aware deserialization as straightforward as standard Deserialize while offering significantly more flexibility.