serde-rs/serde · critical
MapAccess::next_value called before next_key
Error message
MapAccess::next_value called before next_key
What it means
This is a panic (via .expect at serde_core/src/de/value.rs:1383) in the PUBLIC serde_core::de::value::MapDeserializer<I,E>, the generic adapter that turns any Iterator of (K,V) pairs into a Deserializer. The struct (defined at value.rs:1227) holds 'value: Option<Second<I::Item>>' staged by next_key_seed->next_pair and consumed by next_value_seed; calling next_value_seed when it is None means the consuming Visitor/Deserialize broke the MapAccess protocol (next_value before a successful next_key). Because this MapDeserializer is part of the public API, this is the variant application code most often constructs directly via MapDeserializer::new(iter).
Source
Thrown at serde_core/src/de/value.rs:1383
T: de::DeserializeSeed<'de>,
{
match self.next_pair() {
Some((key, value)) => {
self.value = Some(value);
seed.deserialize(key.into_deserializer()).map(Some)
}
None => Ok(None),
}
}
fn next_value_seed<T>(&mut self, seed: T) -> Result<T::Value, Self::Error>
where
T: de::DeserializeSeed<'de>,
{
let value = self.value.take();
// Panic because this indicates a bug in the program rather than an
// expected failure.
let value = value.expect("MapAccess::next_value called before next_key");
seed.deserialize(value.into_deserializer())
}
fn next_entry_seed<TK, TV>(
&mut self,
kseed: TK,
vseed: TV,
) -> Result<Option<(TK::Value, TV::Value)>, Self::Error>
where
TK: de::DeserializeSeed<'de>,
TV: de::DeserializeSeed<'de>,
{
match self.next_pair() {
Some((key, value)) => {
let key = tri!(kseed.deserialize(key.into_deserializer()));
let value = tri!(vseed.deserialize(value.into_deserializer()));
Ok(Some((key, value)))
}View on GitHub (pinned to 747814f7d5)
Solutions
- Fix the consuming Visitor::visit_map so every map.next_value()? is inside 'while let Some(k) = map.next_key()? { .. }' (or 'if let Some(k)') with exactly one next_value per key.
- Drive the map with map.next_entry()? / next_entry_seed returning Option<(K,V)>, which is atomic and cannot trigger this panic.
- If the MapDeserializer is constructed in a test, assert map.next_key() returns Some before asserting on next_value; restructure the test to mirror the derived visit_map loop.
- Replace the hand-written Deserialize with #[derive(Deserialize)] to eliminate the protocol-violation surface entirely.
- Confirm you depend on serde_core (not just serde) and that versions of serde and serde_core match; a skew between the two can route through an unexpected MapDeserializer impl.
Example fix
// before — test feeds MapDeserializer to a visit_map that calls next_value first
let md = MapDeserializer::new(vec![("k", 1)].into_iter());
let v: Foo = Deserialize::deserialize(md)?;
// inside Foo's visit_map:
// let v: i32 = map.next_value()?; // PANIC at value.rs:1383
// after — visit_map pairs key+value (or uses next_entry)
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where A: serde::de::MapAccess<'de>,
{
let mut n = 0;
while let Some((k, v)): (String, i32) = map.next_entry()? {
if k == "k" { n = v; }
}
Ok(Foo(n))
} Defensive patterns
Strategy: validation
Validate before calling
// Validate the consumer BEFORE wiring a public MapDeserializer into it:
// run this against the target Deserialize to confirm it honors the protocol.
// If this panics, the bug is in MyType's Visitor, not in MapDeserializer.
#[cfg(test)]
fn assert_map_protocol_honored() {
use serde::Deserialize;
use serde_core::de::value::MapDeserializer;
let md = MapDeserializer::new(vec![("k".to_string(), 1i64)].into_iter());
// should be Ok; a panic here == next_value-before-next_key in MyType
let _v: MyType = MyType::deserialize(md).expect("protocol honored");
}
// In real visit_map code, use next_entry to make the panic impossible:
// while let Some((k, v)): (String, i32) = map.next_entry()? { .. } Try / catch
// Last-resort safety net around a MapDeserializer-driven deserialize path.
// Fix the offending Visitor; this only prevents the panic from killing the process.
use std::panic::{catch_unwind, AssertUnwindSafe};
use serde_core::de::value::MapDeserializer;
let md = MapDeserializer::new(pairs.into_iter());
let res = catch_unwind(AssertUnwindSafe(|| {
MyType::deserialize(md)
}));
match res {
Ok(Ok(v)) => Ok(v),
Ok(Err(e)) => Err(e.into()),
Err(_) => Err("serde_core MapAccess protocol violation: next_value before next_key".into()),
} Prevention
- When feeding a public MapDeserializer to a Deserialize, drive the consumer's visit_map with next_entry() / next_entry_seed() — atomic, panic-proof.
- Keep serde and serde_core versions in lockstep (serde 1.0.x re-exports from serde_core); a version skew can route through an unexpected MapDeserializer impl.
- In tests, build MapDeserializer from a small known vec of tuples and assert next_key() returns Some before asserting on any next_value().
- Prefer #[derive(Deserialize)] for the target type; hand-written Visitors are the only realistic source of this panic.
- Grep your tests and glue code for 'MapDeserializer::new' and confirm each consumer type's visit_map pairs key+value correctly.
When it happens
Trigger: Constructing MapDeserializer::new(map.into_iter()) (or via IntoDeserializer on a HashMap/BTreeMap) to deserialize into a type whose Visitor::visit_map calls map.next_value()? without a preceding map.next_key()?; or calls next_value twice per key; or calls next_value after next_key returned None. Common in unit tests that feed a custom MapDeserializer to exercise a Deserialize impl, and in glue code that turns a runtime map into a Deserializer for a config/schema type.
Common situations: Tests for a custom Deserialize that build a MapDeserializer from a vec of tuples; config parsers that turn a HashMap into a Deserializer via IntoDeserializer; converting between map representations (toml/json/HashMap) into a typed struct whose Deserialize is hand-written and buggy; bumping serde_core version where this MapDeserializer moved out of serde into serde_core (1.0.x split), changing the panic file/line but not the cause.
Related errors
AI-assisted analysis of serde-rs/serde@747814f7d5 (2026-08-06).
Data as JSON: /data/errors/a7b43e5b74345f09.json.
Report an issue: GitHub.