Loading page…
Rust walkthroughs
Loading page…
serde::de::DeserializeSeed for context-aware deserialization?serde::de::DeserializeSeed is a trait that enables passing external context into the deserialization process, allowing types to be deserialized with access to data that isn't present in the serialized form. While standard Deserialize creates values solely from serialized data, DeserializeSeed carries a "seed" value that deserializers can use to construct types that require additional information—such as registry lookups, configuration data, or references to existing structures. This mechanism is essential for deserializing trait objects, implementing dependency injection during deserialization, and building types that need runtime context not stored in the serialized format.
use serde::de::{DeserializeSeed, Deserializer, Visitor, Error};
use std::marker::PhantomData;
// A simple seed that provides context during deserialization
struct ContextualSeed<'a, T> {
context: &'a str,
_marker: PhantomData<T>,
}
impl<'de, 'a, T> DeserializeSeed<'de> for ContextualSeed<'a, T>
where
T: serde::Deserialize<'de>,
{
type Value = (T, &'a str);
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
// First deserialize the value normally
let value: T = T::deserialize(deserializer)?;
// Then combine with context
Ok((value, self.context))
}
}
fn main() {
let json = "42";
let seed = ContextualSeed::<i32> {
context: "my_context",
_marker: PhantomData,
};
let result: (i32, &str) = seed.deserialize(&mut serde_json::Deserializer::from_str(json)).unwrap();
println!("Value: {}, Context: {}", result.0, result.1);
}DeserializeSeed defines an associated Value type and receives a deserializer to produce a value.
use serde::{Deserialize, de::{DeserializeSeed, Deserializer, Visitor, Error}};
use std::collections::HashMap;
// A registry of item creators
type ItemCreator = fn(serde_json::Value) -> Result<Box<dyn Item>, String>;
trait Item {
fn name(&self) -> &str;
fn as_any(&self) -> &dyn std::any::Any;
}
#[derive(Deserialize)]
struct ItemDef {
#[serde(rename = "type")]
item_type: String,
data: serde_json::Value,
}
// Seed that uses a registry to create items
struct ItemSeed {
registry: HashMap<String, ItemCreator>,
}
impl<'de> DeserializeSeed<'de> for ItemSeed {
type Value = Box<dyn Item>;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
let def = ItemDef::deserialize(deserializer)?;
let creator = self.registry.get(&def.item_type)
.ok_or_else(|| D::Error::custom(format!("Unknown type: {}", def.item_type)))?;
creator(def.data).map_err(D::Error::custom)
}
}
// Concrete item types
struct Sword { name: String, damage: u32 }
impl Item for Sword {
fn name(&self) -> &str { &self.name }
fn as_any(&self) -> &dyn std::any::Any { self }
}
struct Potion { name: String, healing: u32 }
impl Item for Potion {
fn name(&self) -> &str { &self.name }
fn as_any(&self) -> &dyn std::any::Any { self }
}
fn main() {
let mut registry: HashMap<String, ItemCreator> = HashMap::new();
registry.insert("sword".to_string(), |data| {
let name = data["name"].as_str().unwrap().to_string();
let damage = data["damage"].as_u64().unwrap() as u32;
Ok(Box::new(Sword { name, damage }))
});
registry.insert("potion".to_string(), |data| {
let name = data["name"].as_str().unwrap().to_string();
let healing = data["healing"].as_u64().unwrap() as u32;
Ok(Box::new(Potion { name, healing }))
});
let json = r#"{"type": "sword", "data": {"name": "Excalibur", "damage": 50}}"#;
let seed = ItemSeed { registry };
let item: Box<dyn Item> = seed.deserialize(
&mut serde_json::Deserializer::from_str(json)
).unwrap();
println!("Item: {}", item.name());
}The seed provides a registry that maps type names to constructors.
use serde::{Deserialize, de::{DeserializeSeed, Deserializer, Error}};
use std::collections::HashMap;
// User data that references a shared configuration
#[derive(Debug)]
struct User {
id: u32,
name: String,
config_ref: String,
config: &'static Config,
}
#[derive(Debug, Clone)]
struct Config {
theme: String,
language: String,
}
// Seed provides the config registry
struct UserSeed {
configs: HashMap<String, &'static Config>,
}
impl<'de> DeserializeSeed<'de> for UserSeed {
type Value = User;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
// Temp struct to deserialize from JSON
#[derive(Deserialize)]
struct UserDef {
id: u32,
name: String,
config_ref: String,
}
let def = UserDef::deserialize(deserializer)?;
// Look up config from registry
let config = self.configs.get(&def.config_ref)
.ok_or_else(|| D::Error::custom(format!("Unknown config: {}", def.config_ref)))?;
Ok(User {
id: def.id,
name: def.name,
config_ref: def.config_ref,
config: *config,
})
}
}
fn main() {
// In real code, configs would be leaked or stored elsewhere
let config1 = Box::leak(Box::new(Config {
theme: "dark".to_string(),
language: "en".to_string(),
}));
let mut configs: HashMap<String, &'static Config> = HashMap::new();
configs.insert("default".to_string(), config1);
let json = r#"{"id": 1, "name": "Alice", "config_ref": "default"}"#;
let seed = UserSeed { configs };
let user: User = seed.deserialize(
&mut serde_json::Deserializer::from_str(json)
).unwrap();
println!("User: {} (theme: {})", user.name, user.config.theme);
}Deserialized structures can reference external data not stored in the serialized format.
use serde::de::{DeserializeSeed, Deserializer, Visitor, SeqAccess, Error};
use std::marker::PhantomData;
// Context that needs to be passed to each element
struct Context {
multiplier: f64,
}
#[derive(Debug)]
struct ValueWithContext {
value: f64,
result: f64,
}
// Seed that clones context for each element
struct ValueSeed {
context: Context,
}
impl<'de> DeserializeSeed<'de> for ValueSeed {
type Value = ValueWithContext;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
let value: f64 = f64::deserialize(deserializer)?;
Ok(ValueWithContext {
value,
result: value * self.context.multiplier,
})
}
}
// Clone-capable seed for collections
struct SeqSeed {
context: Context,
}
impl<'de> DeserializeSeed<'de> for SeqSeed {
type Value = Vec<ValueWithContext>;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
struct SeqVisitor {
context: Context,
}
impl<'de> Visitor<'de> for SeqVisitor {
type Value = Vec<ValueWithContext>;
fn expecting(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(fmt, "a sequence")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
let mut values = Vec::new();
while let Some(value) = seq.next_element_seed(ValueSeed {
context: Context { multiplier: self.context.multiplier },
})? {
values.push(value);
}
Ok(values)
}
}
deserializer.deserialize_seq(SeqVisitor {
context: self.context,
})
}
}
fn main() {
let json = "[1.0, 2.0, 3.0, 4.0, 5.0]";
let seed = SeqSeed {
context: Context { multiplier: 10.0 },
};
let values: Vec<ValueWithContext> = seed.deserialize(
&mut serde_json::Deserializer::from_str(json)
).unwrap();
for v in values {
println!("{} * 10 = {}", v.value, v.result);
}
}Collections require the seed to be cloned or passed to each element during deserialization.
use serde::de::{DeserializeSeed, Deserializer, Visitor, MapAccess, Error};
// Deserializing into existing structure with context
struct Config {
defaults: Defaults,
values: std::collections::HashMap<String, String>,
}
struct Defaults {
host: String,
port: u16,
}
struct ConfigSeed {
defaults: Defaults,
}
impl<'de> DeserializeSeed<'de> for ConfigSeed {
type Value = Config;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
struct ConfigVisitor {
defaults: Defaults,
}
impl<'de> Visitor<'de> for ConfigVisitor {
type Value = Config;
fn expecting(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(fmt, "a config map")
}
fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
where
M: MapAccess<'de>,
{
let mut values = std::collections::HashMap::new();
while let Some(key) = map.next_key::<String>()? {
let value = map.next_value::<String>()?;
values.insert(key, value);
}
Ok(Config {
defaults: self.defaults,
values,
})
}
}
deserializer.deserialize_map(ConfigVisitor {
defaults: self.defaults,
})
}
}
fn main() {
let json = r#"{"host": "localhost", "port": "8080", "mode": "debug"}"#;
let seed = ConfigSeed {
defaults: Defaults {
host: "0.0.0.0".to_string(),
port: 3000,
},
};
let config: Config = seed.deserialize(
&mut serde_json::Deserializer::from_str(json)
).unwrap();
println!("Defaults: {}:{}", config.defaults.host, config.defaults.port);
println!("Values: {:?}", config.values);
}Context can provide defaults that complement the deserialized values.
use serde::de::{DeserializeSeed, Deserializer, Visitor, MapAccess, SeqAccess, Error};
use std::collections::HashMap;
// Expression tree with variable resolution
#[derive(Debug)]
enum Expr {
Var(String),
Const(i64),
Add(Box<Expr>, Box<Expr>),
Mul(Box<Expr>, Box<Expr>),
}
struct ExprSeed {
variables: HashMap<String, i64>,
}
impl Clone for ExprSeed {
fn clone(&self) -> Self {
ExprSeed {
variables: self.variables.clone(),
}
}
}
impl<'de> DeserializeSeed<'de> for ExprSeed {
type Value = Expr;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
struct ExprVisitor {
variables: HashMap<String, i64>,
}
impl<'de> Visitor<'de> for ExprVisitor {
type Value = Expr;
fn expecting(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(fmt, "an expression")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: Error,
{
// Variable reference or constant
if v.starts_with('$') {
Ok(Expr::Var(v[1..].to_string()))
} else {
v.parse::<i64>()
.map(Expr::Const)
.map_err(|_| E::custom("invalid number"))
}
}
fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
where
M: MapAccess<'de>,
{
let mut op: Option<String> = None;
let mut args: Vec<Expr> = Vec::new();
while let Some(key) = map.next_key::<String>()? {
match key.as_str() {
"op" => op = Some(map.next_value()?),
"args" => {
let seed = ExprSeed {
variables: self.variables.clone(),
};
// Deserialize args as sequence with seed
// (simplified for example)
while let Some(arg) = map.next_value_seed(ExprSeed {
variables: self.variables.clone(),
})? {
args.push(arg);
}
}
_ => { map.next_value::<serde::de::Ignored>()?; }
}
}
let op = op.ok_or_else(|| M::Error::custom("missing op"))?;
match op.as_str() {
"add" => {
let left = args.into_iter().next()
.ok_or_else(|| M::Error::custom("need 2 args"))?;
// Simplified - would need proper 2-arg handling
Ok(Expr::Const(0))
}
"mul" => {
Ok(Expr::Const(0))
}
_ => Err(M::Error::custom("unknown op")),
}
}
}
deserializer.deserialize_any(ExprVisitor {
variables: self.variables,
})
}
}
fn main() {
let mut vars = HashMap::new();
vars.insert("x".to_string(), 10);
vars.insert("y".to_string(), 20);
let json = r#""$x""#; // Simple variable reference
let seed = ExprSeed { variables: vars };
let expr: Expr = seed.deserialize(
&mut serde_json::Deserializer::from_str(json)
).unwrap();
println!("Expr: {:?}", expr);
}Complex recursive structures can propagate context through nested seeds.
use serde::de::{DeserializeSeed, Deserializer, Error};
use std::collections::HashMap;
// Trait object deserialization requires type registry
trait Animal {
fn speak(&self) -> &str;
}
struct Dog { name: String }
impl Animal for Dog {
fn speak(&self) -> &str { "woof" }
}
struct Cat { name: String }
impl Animal for Cat {
fn speak(&self) -> &str { "meow" }
}
#[derive(serde::Deserialize)]
struct AnimalDef {
#[serde(rename = "type")]
animal_type: String,
name: String,
}
type AnimalFactory = fn(String) -> Box<dyn Animal>;
struct AnimalSeed {
registry: HashMap<String, AnimalFactory>,
}
impl<'de> DeserializeSeed<'de> for AnimalSeed {
type Value = Box<dyn Animal>;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
let def = AnimalDef::deserialize(deserializer)?;
let factory = self.registry.get(&def.animal_type)
.ok_or_else(|| D::Error::custom(format!("Unknown animal: {}", def.animal_type)))?;
Ok(factory(def.name))
}
}
fn create_dog(name: String) -> Box<dyn Animal> {
Box::new(Dog { name })
}
fn create_cat(name: String) -> Box<dyn Animal> {
Box::new(Cat { name })
}
fn main() {
let mut registry: HashMap<String, AnimalFactory> = HashMap::new();
registry.insert("dog".to_string(), create_dog);
registry.insert("cat".to_string(), create_cat);
let json = r#"{"type": "dog", "name": "Buddy"}"#;
let seed = AnimalSeed { registry };
let animal: Box<dyn Animal> = seed.deserialize(
&mut serde_json::Deserializer::from_str(json)
).unwrap();
println!("Animal says: {}", animal.speak());
}Trait objects require a registry mapping type identifiers to constructors.
use serde::de::{DeserializeSeed, Deserializer, Error};
// Deserializing data that references external resources
struct Resource {
id: u32,
name: String,
data_ref: &'static str, // References external data
}
struct ResourceSeed {
data_store: &'static HashMap<u32, &'static str>,
}
impl<'de> DeserializeSeed<'de> for ResourceSeed {
type Value = Resource;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
#[derive(serde::Deserialize)]
struct ResourceDef {
id: u32,
name: String,
data_id: u32,
}
let def = ResourceDef::deserialize(deserializer)?;
let data_ref = self.data_store.get(&def.data_id)
.ok_or_else(|| D::Error::custom(format!("Unknown data id: {}", def.data_id)))?;
Ok(Resource {
id: def.id,
name: def.name,
data_ref: *data_ref,
})
}
}
fn main() {
let data: HashMap<u32, &'static str> = [
(1, "static data 1"),
(2, "static data 2"),
].into_iter().map(|(k, v)| (k, Box::leak(v.into_boxed_str()))).collect();
// Leak for static lifetime
let static_data: &'static HashMap<u32, &'static str> = Box::leak(Box::new(data));
let json = r#"{"id": 1, "name": "Resource A", "data_id": 1}"#;
let seed = ResourceSeed { data_store: static_data };
let resource: Resource = seed.deserialize(
&mut serde_json::Deserializer::from_str(json)
).unwrap();
println!("Resource: {} - {}", resource.name, resource.data_ref);
}Seeds can provide references to external data structures with appropriate lifetimes.
use serde::{Deserialize, de::{DeserializeSeed, Deserializer}};
// Standard Deserialize: no external context
#[derive(Deserialize, Debug)]
struct Point {
x: f64,
y: f64,
}
// DeserializeSeed: with external context
struct PointWithScale {
point: Point,
scale: f64,
}
struct PointSeed {
scale: f64,
}
impl<'de> DeserializeSeed<'de> for PointSeed {
type Value = PointWithScale;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
let point: Point = Point::deserialize(deserializer)?;
Ok(PointWithScale {
point,
scale: self.scale,
})
}
}
impl std::fmt::Display for PointWithScale {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "Point({}, {}) scaled by {}", self.point.x, self.point.y, self.scale)
}
}
fn main() {
// Standard Deserialize
let json = r#"{"x": 10.0, "y": 20.0}"#;
let point: Point = serde_json::from_str(json).unwrap();
println!("Standard deserialize: {:?}", point);
// With DeserializeSeed
let seed = PointSeed { scale: 2.0 };
let scaled: PointWithScale = seed.deserialize(
&mut serde_json::Deserializer::from_str(json)
).unwrap();
println!("With seed: {}", scaled);
}Deserialize creates values from serialized data alone; DeserializeSeed adds context.
use serde::de::{DeserializeSeed, Deserializer, Visitor, SeqAccess, Error};
// Deserialize each element with context
struct Item {
id: u32,
name: String,
computed: String, // Derived from context + data
}
struct ItemSeed {
prefix: String,
}
impl<'de> DeserializeSeed<'de> for ItemSeed {
type Value = Item;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
#[derive(serde::Deserialize)]
struct ItemDef {
id: u32,
name: String,
}
let def = ItemDef::deserialize(deserializer)?;
Ok(Item {
id: def.id,
name: def.name.clone(),
computed: format!("{}-{}", self.prefix, def.name),
})
}
}
struct ItemsSeed {
prefix: String,
}
impl<'de> DeserializeSeed<'de> for ItemsSeed {
type Value = Vec<Item>;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
struct ItemsVisitor {
prefix: String,
}
impl<'de> Visitor<'de> for ItemsVisitor {
type Value = Vec<Item>;
fn expecting(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(fmt, "a sequence of items")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
let mut items = Vec::new();
let prefix = self.prefix;
while let Some(item) = seq.next_element_seed(ItemSeed {
prefix: prefix.clone(),
})? {
items.push(item);
}
Ok(items)
}
}
deserializer.deserialize_seq(ItemsVisitor {
prefix: self.prefix,
})
}
}
fn main() {
let json = r#"[{"id": 1, "name": "apple"}, {"id": 2, "name": "banana"}]"#;
let seed = ItemsSeed {
prefix: "fruit".to_string(),
};
let items: Vec<Item> = seed.deserialize(
&mut serde_json::Deserializer::from_str(json)
).unwrap();
for item in items {
println!("{}: {} -> {}", item.id, item.name, item.computed);
}
}SeqAccess::next_element_seed deserializes each element with its own seed.
use serde::de::{DeserializeSeed, Deserializer, Visitor, MapAccess, Error};
struct KeyValueWithContext {
key: String,
value: String,
namespace: String,
}
struct MapSeed {
namespace: String,
}
impl<'de> DeserializeSeed<'de> for MapSeed {
type Value = Vec<KeyValueWithContext>;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
struct MapVisitor {
namespace: String,
}
impl<'de> Visitor<'de> for MapVisitor {
type Value = Vec<KeyValueWithContext>;
fn expecting(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(fmt, "a map")
}
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: MapAccess<'de>,
{
let mut entries = Vec::new();
while let Some(key) = map.next_key::<String>()? {
let value = map.next_value::<String>()?;
entries.push(KeyValueWithContext {
key,
value,
namespace: self.namespace.clone(),
});
}
Ok(entries)
}
}
deserializer.deserialize_map(MapVisitor {
namespace: self.namespace,
})
}
}
fn main() {
let json = r#"{"key1": "value1", "key2": "value2"}"#;
let seed = MapSeed {
namespace: "config".to_string(),
};
let entries: Vec<KeyValueWithContext> = seed.deserialize(
&mut serde_json::Deserializer::from_str(json)
).unwrap();
for entry in entries {
println!("[{}] {} = {}", entry.namespace, entry.key, entry.value);
}
}Map keys and values can be deserialized with context using MapAccess.
use serde::de::{DeserializeSeed, Deserializer, Error};
use std::collections::HashMap;
// Services registry for dependency injection
trait Service {
fn name(&self) -> &str;
}
struct Database { name: String }
impl Service for Database {
fn name(&self) -> &str { &self.name }
}
struct Cache { name: String }
impl Service for Database {
fn name(&self) -> &str { &self.name }
}
impl Service for Cache {
fn name(&self) -> &str { &self.name }
}
#[derive(serde::Deserialize)]
struct ServiceRef {
service_type: String,
service_name: String,
}
struct ServiceSeed {
services: HashMap<String, Box<dyn Service>>,
}
impl<'de> DeserializeSeed<'de> for ServiceSeed {
type Value = Box<dyn Service>;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
let ref_def = ServiceRef::deserialize(deserializer)?;
// In a real implementation, you'd look up or construct the service
// based on the type and name
Err(D::Error::custom("Service lookup not implemented in example"))
}
}
fn main() {
// Example demonstrates the pattern - actual implementation would
// need proper service construction logic
let mut services: HashMap<String, Box<dyn Service>> = HashMap::new();
services.insert("db_main".to_string(), Box::new(Database {
name: "main_db".to_string(),
}));
services.insert("cache_local".to_string(), Box::new(Cache {
name: "local_cache".to_string(),
}));
// JSON would reference services by name
// {"service_type": "database", "service_name": "main"}
}DeserializeSeed enables service locator patterns during deserialization.
DeserializeSeed vs Deserialize:
| Aspect | Deserialize | DeserializeSeed |
|--------|---------------|-------------------|
| Context | None | Seed value |
| Use case | Self-contained data | Context-dependent data |
| Trait method | deserialize<D> | deserialize_seed<D, S> |
| Value type | Self | Self::Value |
Key use cases:
| Use Case | What Seed Provides | |----------|---------------------| | Registry lookup | Type → constructor mapping | | Defaults | Default values for missing fields | | References | External data structures | | Context | Runtime configuration | | Validation | External validators |
Seed lifecycle:
Create seed with context
↓
Call seed.deserialize(deserializer)
↓
Seed uses context during deserialization
↓
Returns constructed value
Collection deserialization:
| Method | Purpose |
|--------|---------|
| next_element_seed | Deserialize element with seed |
| next_value_seed | Deserialize map value with seed |
| next_key_seed | Deserialize map key with seed |
Key insight: serde::de::DeserializeSeed bridges the gap between serialized data and runtime context, enabling deserialization that requires information not present in the serialized format. This is essential for implementing type registries (deserializing trait objects), resolving references (IDs to external objects), providing defaults (configuration inheritance), and implementing dependency injection during deserialization. The seed pattern requires implementing DeserializeSeed with an associated Value type, then using SeqAccess::next_element_seed and MapAccess::next_value_seed to propagate the seed through nested structures. Unlike standard Deserialize which creates values purely from serialized bytes, DeserializeSeed produces values that incorporate external context, making it the foundation for advanced deserialization patterns in complex systems.