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

  1. 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.
  2. Drive the map with map.next_entry()? / next_entry_seed returning Option<(K,V)>, which is atomic and cannot trigger this panic.
  3. 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.
  4. Replace the hand-written Deserialize with #[derive(Deserialize)] to eliminate the protocol-violation surface entirely.
  5. 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 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.