{"id":"85f738aa7d0cefe1","repo":"serde-rs/serde","slug":"mapaccess-next-value-called-before-next-key","errorCode":null,"errorMessage":"MapAccess::next_value called before next_key","messagePattern":"MapAccess::next_value called before next_key","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"serde/src/private/de.rs","lineNumber":1716,"sourceCode":"            T: DeserializeSeed<'de>,\n        {\n            match self.next_pair() {\n                Some((key, value)) => {\n                    self.value = Some(value);\n                    seed.deserialize(ContentDeserializer::new(key)).map(Some)\n                }\n                None => Ok(None),\n            }\n        }\n\n        fn next_value_seed<T>(&mut self, seed: T) -> Result<T::Value, Self::Error>\n        where\n            T: DeserializeSeed<'de>,\n        {\n            let value = self.value.take();\n            // Panic because this indicates a bug in the program rather than an\n            // expected failure.\n            let value = value.expect(\"MapAccess::next_value called before next_key\");\n            seed.deserialize(ContentDeserializer::new(value))\n        }\n\n        fn next_entry_seed<TK, TV>(\n            &mut self,\n            kseed: TK,\n            vseed: TV,\n        ) -> Result<Option<(TK::Value, TV::Value)>, Self::Error>\n        where\n            TK: DeserializeSeed<'de>,\n            TV: DeserializeSeed<'de>,\n        {\n            match self.next_pair() {\n                Some((key, value)) => {\n                    let key = tri!(kseed.deserialize(ContentDeserializer::new(key)));\n                    let value = tri!(vseed.deserialize(ContentDeserializer::new(value)));\n                    Ok(Some((key, value)))\n                }","sourceCodeStart":1698,"sourceCodeEnd":1734,"githubUrl":"https://github.com/serde-rs/serde/blob/747814f7d5fbab872df3b02f070c165b91bde062/serde/src/private/de.rs#L1698-L1734","documentation":"This is a panic (via .expect at serde/src/private/de.rs:1716) inside serde's internal owned-content MapAccess, which buffers Vec<(Content, Content)> to replay map entries during #[serde(flatten)], internally/adjacently tagged enums, and Content-based deserialization. The 'value' slot (set by next_key_seed -> next_pair and taken by next_value_seed) is None, meaning next_value_seed ran without a preceding successful next_key_seed. Serde panics rather than returning an Err because the MapAccess contract is absolute: next_key must be called first and yield Some before next_value is called exactly once, and a violation is a bug in a Visitor/Deserialize impl, not malformed input.","triggerScenarios":"A hand-written Visitor::visit_map<M: MapAccess> whose body calls map.next_value()? without first calling map.next_key()?; or calls next_value() twice for one next_key(); or ignores a None from next_key() and still calls next_value(). The panic is reached through serde's content-buffering layer, so it surfaces when the offending type sits behind #[serde(flatten)], an internally-tagged enum (#[serde(tag = \"type\")]), an adjacently-tagged enum, or any field whose derived Deserialize replays buffered Content via this owned MapDeserializer.","commonSituations":"Adding #[serde(flatten)] to a struct whose nested type has a custom Deserialize; porting a Visitor from an older serde where the key/value call order was different; a custom Deserialize for a value nested under a tagged enum whose visit_map skips the key; refactoring a visit_map loop and accidentally hoisting next_value outside the while-let-Some(key) block; a Deserialize impl that calls next_value in an else branch after next_key returned None.","solutions":["Audit every Visitor::visit_map for this type: ensure the only calls to map.next_value()/next_value_seed() occur inside a 'while let Some(key) = map.next_key()? { ... }' (or 'if let Some(key)') block, exactly one next_value per key.","Prefer the derived Deserialize (#[derive(Deserialize)]) or next_entry/next_entry_seed over manual next_key/next_value pairing, which removes the opportunity to break the protocol.","If you must hand-write the loop, use map.next_entry()? which yields Option<(K,V)> atomically and cannot trigger this panic.","Temporarily remove #[serde(flatten)] or the tagged-enum attribute to isolate whether the bug is in the buffering path or in the leaf Deserialize; the panic location (owned Content variant) confirms the buffering layer is involved.","Upgrade serde/serde_derive to the latest 1.0.x; older releases had content-buffering bugs around flatten and tagged enums that could surface this panic from otherwise-correct derived code."],"exampleFix":"// before — broken: next_value with no preceding next_key\nfn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>\nwhere A: serde::de::MapAccess<'de>,\n{\n    let v: i32 = map.next_value()?; // PANIC at de.rs:1716\n    Ok(Foo(v))\n}\n\n// after — correct pairing (or use next_entry)\nfn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>\nwhere A: serde::de::MapAccess<'de>,\n{\n    let mut out = Foo::default();\n    while let Some(k) = map.next_key::<String>()? {\n        match k.as_str() {\n            \"n\" => out.n = map.next_value()?, // ok: key just yielded\n            _   => { let _: serde::de::IgnoredAny = map.next_value()?; }\n        }\n    }\n    Ok(out)\n}","handlingStrategy":"validation","validationCode":"// Validate at authoring time: every next_value MUST be inside this loop.\n// Use this idiom in every Visitor::visit_map to make the panic structurally\n// impossible (no bare next_value can exist outside a successful next_key).\nfn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>\nwhere A: serde::de::MapAccess<'de>,\n{\n    while let Some(_key) = map.next_key::<serde::de::IgnoredAny>()? {\n        let _val: serde::de::IgnoredAny = map.next_value()?; // ok: paired\n    }\n    Ok(Default::default())\n}","typeGuard":null,"tryCatchPattern":"// Last-resort safety net ONLY: catch the panic at a process boundary.\n// A panic from de.rs:1716 is a logic bug; do NOT use this to mask it in normal\n// code paths. Fix the offending Visitor/Deserialize instead.\nuse std::panic::catch_unwind;\nlet res = catch_unwind(|| serde_json::from_str::<MyType>(input));\nmatch res {\n    Ok(Ok(v)) => v,\n    Ok(Err(e)) => /* normal serde de error */ return Err(e.into()),\n    Ok(_) => unreachable!(),\n    Err(_panic) => /* MapAccess protocol violation in a Deserialize impl */\n        return Err(\"deserializer protocol violation: next_value before next_key\".into()),\n}","preventionTips":["Always pair map.next_key() with exactly one map.next_value() inside the same loop iteration; never call next_value() unconditionally or after next_key() returned None.","Prefer map.next_entry() / next_entry_seed() over manual next_key + next_value — it is atomic and cannot panic.","Prefer #[derive(Deserialize)] over hand-written Visitor for map types; only hand-write visit_map when truly necessary.","When using #[serde(flatten)] or tagged enums, test any nested custom Deserialize with realistic inputs, since the content-buffering layer (errors at de.rs:1716/2662) is what surfaces protocol bugs there.","Add a clippy/msrv-aware review step: grep your crate for 'next_value' and confirm each call site is lexically inside a 'while let Some(_) = .*next_key' or 'if let Some(_) = .*next_key' block."],"tags":["serde","rust","deserialization","mapaccess","panic","flatten","tagged-enum"],"analyzedSha":"747814f7d5fbab872df3b02f070c165b91bde062","analyzedAt":"2026-08-06T01:20:43.063Z","schemaVersion":2}