Loading pageâŚ
Rust walkthroughs
Loading pageâŚ
serde::MapAccess::next_value_seed enable context-aware deserialization of map values?The serde::MapAccess::next_value_seed method enables passing external context or "seed" data into the deserialization of map values, allowing deserializers to make decisions based on information outside the serialized data itself. While next_value() deserializes values using only the data from the input stream, next_value_seed() accepts a DeserializeSeed implementation that can carry additional stateâsuch as configuration settings, reference data, or computed contextâthat influences how each value is interpreted. This pattern is fundamental to advanced deserialization scenarios like deserializing heterogeneous collections, implementing circular reference resolution, or applying transformation rules based on external schemas.
use serde::de::{MapAccess, Visitor, DeserializeSeed};
use std::collections::HashMap;
// MapAccess is implemented by deserializers to iterate over map entries
struct HashMapAccess<'de, K, V> {
entries: std::iter::Peekable<std::collections::hash_map::IntoIter<K, V>>,
_marker: std::marker::PhantomData<&'de ()>,
}
// Basic map access pattern
fn example_map_access() {
// When deserializing a map, the format (JSON, etc.) provides MapAccess
// that yields key-value pairs one at a time
let json_data = r#"{"a": 1, "b": 2, "c": 3}"#;
// The deserializer creates a MapAccess implementation
// that can iterate over entries
}MapAccess provides iteration over map entries during deserialization.
use serde::de::{MapAccess, DeserializeSeed};
use std::collections::HashMap;
// Trait definition (simplified)
trait MapAccess<'de> {
type Error;
// Basic: deserialize value using only input data
fn next_value<V>(&mut self) -> Result<V, Self::Error>
where
V: serde::Deserialize<'de>;
// Advanced: deserialize value with seed context
fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value, Self::Error>
where
V: DeserializeSeed<'de>;
}
// next_value() is implemented using next_value_seed()
fn next_value<V>(&mut self) -> Result<V, Self::Error>
where
V: serde::Deserialize<'de>,
{
// Uses a trivial seed that ignores context
self.next_value_seed(serde::de::IgnoredAny)
}next_value_seed is the primitive; next_value wraps it with a no-op seed.
use serde::de::{DeserializeSeed, Deserializer, Visitor};
use std::fmt;
// A seed that provides context during deserialization
struct MultiplierSeed {
factor: i32,
}
impl<'de> DeserializeSeed<'de> for MultiplierSeed {
type Value = i32;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
// Use the seed's context during deserialization
deserializer.deserialize_i32(MultiplierVisitor { factor: self.factor })
}
}
struct MultiplierVisitor {
factor: i32,
}
impl<'de> Visitor<'de> for MultiplierVisitor {
type Value = i32;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(formatter, "an integer")
}
fn visit_i32<E>(self, v: i32) -> Result<i32, E>
where
E: serde::de::Error,
{
// Apply the multiplier from the seed
Ok(v * self.factor)
}
}DeserializeSeed carries additional data into the deserialization process.
use serde::de::{MapAccess, Visitor, DeserializeSeed};
use std::collections::HashMap;
use std::fmt;
// Visitor that uses seeded deserialization for map values
struct ContextualMapVisitor {
multiplier: i32,
}
impl<'de> Visitor<'de> for ContextualMapVisitor {
type Value = HashMap<String, i32>;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "a map with integer values")
}
fn visit_map<A>(self, mut access: A) -> Result<Self::Value, A::Error>
where
A: MapAccess<'de>,
{
let mut map = HashMap::new();
while let Some(key) = access.next_key()? {
// Use next_value_seed to pass context to value deserialization
let seed = MultiplierSeed { factor: self.multiplier };
let value = access.next_value_seed(seed)?;
map.insert(key, value);
}
Ok(map)
}
}
// Now values are deserialized with context
fn deserialize_with_context<'de, D>(deserializer: D) -> Result<HashMap<String, i32>, D::Error>
where
D: Deserializer<'de>,
{
let visitor = ContextualMapVisitor { multiplier: 2 };
deserializer.deserialize_map(visitor)
}
// Usage: {"a": 1, "b": 2} becomes {"a": 2, "b": 4}The visitor creates seeds for each value, passing context through next_value_seed.
use serde::de::{MapAccess, Visitor, DeserializeSeed, Deserializer, Error};
use std::collections::HashMap;
use std::fmt;
// Different types we can deserialize
#[derive(Debug, Clone)]
enum Value {
Number(i64),
Text(String),
Bool(bool),
}
// Registry that maps type names to deserializers
struct TypeRegistry {
types: HashMap<String, fn(serde::de::value::Content<'_>) -> Value>,
}
// Seed that knows which type to deserialize
struct TypedValueSeed {
type_name: String,
}
impl<'de> DeserializeSeed<'de> for TypedValueSeed {
type Value = Value;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
match self.type_name.as_str() {
"number" => deserializer.deserialize_i64(NumberVisitor).map(Value::Number),
"text" => deserializer.deserialize_str(TextVisitor).map(Value::Text),
"bool" => deserializer.deserialize_bool(BoolVisitor).map(Value::Bool),
_ => Err(D::Error::custom(format!("unknown type: {}", self.type_name))),
}
}
}
struct NumberVisitor;
struct TextVisitor;
struct BoolVisitor;
impl<'de> Visitor<'de> for NumberVisitor {
type Value = i64;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "a number") }
fn visit_i64<E>(self, v: i64) -> Result<i64, E> { Ok(v) }
}
impl<'de> Visitor<'de> for TextVisitor {
type Value = String;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "a string") }
fn visit_str<E>(self, v: &str) -> Result<String, E> where E: Error { Ok(v.to_owned()) }
}
impl<'de> Visitor<'de> for BoolVisitor {
type Value = bool;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "a boolean") }
fn visit_bool<E>(self, v: bool) -> Result<bool, E> { Ok(v) }
}Seeds enable deserializing values based on keys or external schema.
use serde::de::{MapAccess, Visitor, DeserializeSeed, Deserializer, Error};
use std::collections::HashMap;
use std::fmt;
// Schema: different keys have different value types
// {"count": 42, "name": "item", "enabled": true}
struct SchemaAwareVisitor;
impl<'de> Visitor<'de> for SchemaAwareVisitor {
type Value = HashMap<String, serde_json::Value>;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "a map with typed values")
}
fn visit_map<A>(self, mut access: A) -> Result<Self::Value, A::Error>
where
A: MapAccess<'de>,
{
let mut map = HashMap::new();
while let Some(key) = access.next_key::<String>()? {
// Use key to determine how to deserialize value
let seed = KeyDependentSeed { key: key.clone() };
let value = access.next_value_seed(seed)?;
map.insert(key, value);
}
Ok(map)
}
}
struct KeyDependentSeed {
key: String,
}
impl<'de> DeserializeSeed<'de> for KeyDependentSeed {
type Value = serde_json::Value;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
// Deserialize differently based on key
match self.key.as_str() {
"count" => {
let n = i32::deserialize(deserializer)?;
Ok(serde_json::Value::Number(n.into()))
}
"name" => {
let s = String::deserialize(deserializer)?;
Ok(serde_json::Value::String(s))
}
"enabled" => {
let b = bool::deserialize(deserializer)?;
Ok(serde_json::Value::Bool(b))
}
_ => {
// Unknown key: accept any value
Ok(serde_json::Value::Null)
}
}
}
}The seed receives key information to inform value deserialization.
use serde::de::{MapAccess, Visitor, DeserializeSeed, Deserializer};
use std::collections::HashMap;
use std::fmt;
// Configuration that affects deserialization
struct DeserializationConfig {
strict_mode: bool,
default_on_missing: bool,
timezone: String,
}
struct ConfiguredVisitor {
config: DeserializationConfig,
}
impl<'de> Visitor<'de> for ConfiguredVisitor {
type Value = HashMap<String, String>;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "a map of strings")
}
fn visit_map<A>(self, mut access: A) -> Result<Self::Value, A::Error>
where
A: MapAccess<'de>,
{
let mut map = HashMap::new();
while let Some(key) = access.next_key()? {
let seed = ConfiguredValueSeed {
config: &self.config,
key: key.clone(),
};
let value = access.next_value_seed(seed)?;
map.insert(key, value);
}
Ok(map)
}
}
struct ConfiguredValueSeed<'a> {
config: &'a DeserializationConfig,
key: String,
}
impl<'de> DeserializeSeed<'de> for ConfiguredValueSeed<'_> {
type Value = String;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
// Apply configuration during deserialization
if self.config.strict_mode {
// In strict mode, validate string content
let s = String::deserialize(deserializer)?;
if s.is_empty() {
return Err(serde::de::Error::custom("empty string not allowed"));
}
Ok(s)
} else if self.config.default_on_missing {
// Accept null or missing as default
Ok(String::deserialize(deserializer).unwrap_or_default())
} else {
String::deserialize(deserializer)
}
}
}Configuration passed through seeds affects deserialization behavior.
use serde::de::{MapAccess, Visitor, DeserializeSeed, Deserializer};
use std::collections::HashMap;
use std::cell::RefCell;
use std::rc::Rc;
// Graph structure that may have cycles
struct Node {
id: String,
connections: Vec<Rc<RefCell<Node>>>,
}
// Context for resolving references
struct DeserializationContext {
nodes: HashMap<String, Rc<RefCell<Node>>>,
pending_refs: Vec<(String, Rc<RefCell<Node>>)>,
}
struct GraphVisitor {
context: Rc<RefCell<DeserializationContext>>,
}
impl<'de> Visitor<'de> for GraphVisitor {
type Value = HashMap<String, Rc<RefCell<Node>>>;
fn visit_map<A>(self, mut access: A) -> Result<Self::Value, A::Error>
where
A: MapAccess<'de>,
{
let mut nodes = HashMap::new();
// First pass: create all nodes
while let Some(id) = access.next_key::<String>()? {
// Use seed that can resolve references
let seed = NodeSeed {
context: self.context.clone(),
};
let node = access.next_value_seed(seed)?;
nodes.insert(id, node);
}
// Second pass: resolve references
// (would be implemented in actual code)
Ok(nodes)
}
}
struct NodeSeed {
context: Rc<RefCell<DeserializationContext>>,
}
impl<'de> DeserializeSeed<'de> for NodeSeed {
type Value = Rc<RefCell<Node>>;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
// Deserialize node and track it for reference resolution
// The context allows resolving forward references
todo!()
}
}Seeds carry context needed for complex reference resolution.
use serde::de::{DeserializeSeed, Deserializer, Visitor};
use std::fmt;
use std::marker::PhantomData;
// Phantom types to track deserialization mode
struct Strict;
struct Lenient;
// Seed that knows the deserialization mode
struct ModeAwareSeed<Mode> {
_mode: PhantomData<Mode>,
}
impl<'de> DeserializeSeed<'de> for ModeAwareSeed<Strict> {
type Value = i32;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
// Strict: only accept integer values
deserializer.deserialize_i32(StrictIntVisitor)
}
}
impl<'de> DeserializeSeed<'de> for ModeAwareSeed<Lenient> {
type Value = i32;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
// Lenient: accept integer or string that parses
deserializer.deserialize_any(LenientIntVisitor)
}
}
struct StrictIntVisitor;
struct LenientIntVisitor;
impl<'de> Visitor<'de> for StrictIntVisitor {
type Value = i32;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "an integer") }
fn visit_i32<E>(self, v: i32) -> Result<i32, E> { Ok(v) }
}
impl<'de> Visitor<'de> for LenientIntVisitor {
type Value = i32;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "an integer or numeric string") }
fn visit_i32<E>(self, v: i32) -> Result<i32, E> { Ok(v) }
fn visit_str<E>(self, v: &str) -> Result<i32, E>
where
E: serde::de::Error,
{
v.parse().map_err(|_| E::custom("not a number"))
}
}Phantom types in seeds encode deserialization behavior at compile time.
use serde::de::{MapAccess, Visitor, DeserializeSeed, Deserializer, Error};
use std::collections::HashMap;
use std::fmt;
// Polymorphic types based on "type" field
#[derive(Debug)]
enum Message {
Ping { id: u64 },
Pong { id: u64, reply_to: u64 },
Data { id: u64, content: String },
}
struct MessageVisitor;
impl<'de> Visitor<'de> for MessageVisitor {
type Value = Message;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "a message object with type field")
}
fn visit_map<A>(self, mut access: A) -> Result<Self::Value, A::Error>
where
A: MapAccess<'de>,
{
let mut type_name: Option<String> = None;
let mut fields: HashMap<String, serde_json::Value> = HashMap::new();
// First pass: collect all fields
while let Some(key) = access.next_key::<String>()? {
if key == "type" {
type_name = Some(access.next_value()?);
} else {
fields.insert(key, access.next_value()?);
}
}
let type_name = type_name.ok_or_else(|| A::Error::custom("missing type field"))?;
// Use type to construct appropriate variant
match type_name.as_str() {
"ping" => {
let id = fields.get("id")
.and_then(|v| v.as_u64())
.ok_or_else(|| A::Error::custom("missing id"))?;
Ok(Message::Ping { id })
}
"pong" => {
let id = fields.get("id")
.and_then(|v| v.as_u64())
.ok_or_else(|| A::Error::custom("missing id"))?;
let reply_to = fields.get("reply_to")
.and_then(|v| v.as_u64())
.ok_or_else(|| A::Error::custom("missing reply_to"))?;
Ok(Message::Pong { id, reply_to })
}
"data" => {
let id = fields.get("id")
.and_then(|v| v.as_u64())
.ok_or_else(|| A::Error::custom("missing id"))?;
let content = fields.get("content")
.and_then(|v| v.as_str())
.ok_or_else(|| A::Error::custom("missing content"))?
.to_string();
Ok(Message::Data { id, content })
}
_ => Err(A::Error::custom(format!("unknown type: {}", type_name))),
}
}
}The visitor collects fields first, then uses type information to construct the variant.
use serde::de::{MapAccess, Visitor, DeserializeSeed, Deserializer};
use std::collections::HashMap;
use std::fmt;
// Schema-driven deserialization where keys determine value types
struct SchemaDrivenVisitor {
schema: HashMap<String, ValueType>,
}
#[derive(Clone)]
enum ValueType {
Integer,
Float,
String,
Boolean,
}
impl<'de> Visitor<'de> for SchemaDrivenVisitor {
type Value = HashMap<String, TypedValue>;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "a map conforming to schema")
}
fn visit_map<A>(self, mut access: A) -> Result<Self::Value, A::Error>
where
A: MapAccess<'de>,
{
let mut result = HashMap::new();
while let Some(key) = access.next_key::<String>()? {
// Look up expected type from schema
let expected_type = self.schema.get(&key)
.cloned()
.unwrap_or(ValueType::String); // Default to string
// Create seed with type information
let seed = SchemaValueSeed { expected_type };
let value = access.next_value_seed(seed)?;
result.insert(key, value);
}
Ok(result)
}
}
#[derive(Debug)]
enum TypedValue {
Integer(i64),
Float(f64),
String(String),
Boolean(bool),
}
struct SchemaValueSeed {
expected_type: ValueType,
}
impl<'de> DeserializeSeed<'de> for SchemaValueSeed {
type Value = TypedValue;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
match self.expected_type {
ValueType::Integer => {
let v = i64::deserialize(deserializer)?;
Ok(TypedValue::Integer(v))
}
ValueType::Float => {
let v = f64::deserialize(deserializer)?;
Ok(TypedValue::Float(v))
}
ValueType::String => {
let v = String::deserialize(deserializer)?;
Ok(TypedValue::String(v))
}
ValueType::Boolean => {
let v = bool::deserialize(deserializer)?;
Ok(TypedValue::Boolean(v))
}
}
}
}Schema information flows from the visitor to individual value seeds.
next_value vs next_value_seed:
| Method | Input Source | Context | Use Case |
|--------|---------------|---------|----------|
| next_value | Input data only | None | Standard deserialization |
| next_value_seed | Input + seed | External state | Context-aware deserialization |
What seeds enable:
Key insight: next_value_seed is the fundamental primitive for context-aware map deserialization. While next_value assumes values can be deserialized from their serialized representation alone, next_value_seed recognizes that real-world deserialization often needs additional contextâschema information, configuration settings, or reference state. The seed pattern makes this context explicit: instead of calling a type's Deserialize implementation directly, you pass a DeserializeSeed that can carry arbitrary state and use it during deserialization. This transforms deserialization from a pure data-to-value transformation into a context-aware process where external information influences how values are constructed. The pattern is especially valuable for polymorphic deserialization, schema validation, and complex reference graphsâscenarios where the serialized data alone doesn't fully specify how to construct the result.