{"id":"1ca774dc2e1ca396","repo":"serde-rs/serde","slug":"serialize-value-called-before-serialize-key","errorCode":null,"errorMessage":"serialize_value called before serialize_key","messagePattern":"serialize_value called before serialize_key","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"serde/src/private/ser.rs","lineNumber":916,"sourceCode":"        type Error = E;\n\n        fn serialize_key<T>(&mut self, key: &T) -> Result<(), E>\n        where\n            T: ?Sized + Serialize,\n        {\n            let key = tri!(key.serialize(ContentSerializer::<E>::new()));\n            self.key = Some(key);\n            Ok(())\n        }\n\n        fn serialize_value<T>(&mut self, value: &T) -> Result<(), E>\n        where\n            T: ?Sized + Serialize,\n        {\n            let key = self\n                .key\n                .take()\n                .expect(\"serialize_value called before serialize_key\");\n            let value = tri!(value.serialize(ContentSerializer::<E>::new()));\n            self.entries.push((key, value));\n            Ok(())\n        }\n\n        fn end(self) -> Result<Content, E> {\n            Ok(Content::Map(self.entries))\n        }\n\n        fn serialize_entry<K, V>(&mut self, key: &K, value: &V) -> Result<(), E>\n        where\n            K: ?Sized + Serialize,\n            V: ?Sized + Serialize,\n        {\n            let key = tri!(key.serialize(ContentSerializer::<E>::new()));\n            let value = tri!(value.serialize(ContentSerializer::<E>::new()));\n            self.entries.push((key, value));\n            Ok(())","sourceCodeStart":898,"sourceCodeEnd":934,"githubUrl":"https://github.com/serde-rs/serde/blob/747814f7d5fbab872df3b02f070c165b91bde062/serde/src/private/ser.rs#L898-L934","documentation":"This is a panic (via .expect at serde/src/private/ser.rs:916) in the content-buffering SerializeMap used by serde's Content serializer. The struct holds 'key: Option<Content>' set by serialize_key and taken by serialize_value; calling serialize_value when key is None means no key was staged first. Serde enforces the SerializeMap contract by panicking because the correct usage is strictly paired: serialize_key then serialize_value, once per entry, or a single serialize_entry call for both.","triggerScenarios":"A hand-written impl Serialize for a map-like type that obtains a SerializeMap via serializer.serialize_map(len)? and calls map.serialize_value(&v)? before any map.serialize_key(&k)?; or calls serialize_value twice without an intervening serialize_key; or loops over values while forgetting to emit keys. Also reachable through serde's content-buffering serializer (used by tagged/adjacently-tagged enum representation) if a nested custom Serialize emits values out of order.","commonSituations":"Writing a custom Serialize for a HashMap-like or associative container; refactoring a serialize_map loop and inverting the key/value call order; implementing Serialize for a newtype wrapping a map and calling serialize_value first by mistake; a tagged enum whose variant payload Serialize is custom and emits value-before-key through the content serializer.","solutions":["Ensure every map.serialize_value(&v)? is immediately preceded by a map.serialize_key(&k)? for the same entry, in that order, exactly once each.","Prefer map.serialize_entry(&k, &v)? which emits key and value atomically and cannot trigger this panic.","Audit any 'continue'/'?'/'return' between serialize_key and serialize_value that could skip one half of the pair.","Where possible, derive Serialize (#[derive(Serialize)]) for map-like types instead of hand-writing the SerializeMap driving loop."],"exampleFix":"// before — broken: value emitted before key\nlet mut m = serializer.serialize_map(Some(1))?;\nm.serialize_value(&42)?;        // PANIC at ser.rs:916\nm.serialize_key(\"answer\")?;\nm.end()\n\n// after — use serialize_entry (or key-then-value)\nlet mut m = serializer.serialize_map(Some(1))?;\nm.serialize_entry(\"answer\", &42)?;  // key+value atomically\nm.end()","handlingStrategy":"validation","validationCode":"// Validate at authoring time: drive SerializeMap with serialize_entry only,\n// which makes the 'value before key' panic structurally impossible.\nfn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>\nwhere S: serde::Serializer,\n{\n    let mut m = serializer.serialize_map(Some(self.inner.len()))?;\n    for (k, v) in &self.inner {\n        m.serialize_entry(k, v)?; // atomic key+value — cannot panic\n    }\n    m.end()\n}","typeGuard":null,"tryCatchPattern":"// Last-resort only; catches the ser.rs:916 panic at a boundary. The real fix is\n// to emit key before value (or use serialize_entry).\nuse std::panic::{catch_unwind, AssertUnwindSafe};\nlet res = catch_unwind(AssertUnwindSafe(|| value.serialize(&mut serializer)));\nmatch res {\n    Ok(Ok(v)) => Ok(v),\n    Ok(Err(e)) => Err(e.into()),\n    Err(_) => Err(\"serde SerializeMap protocol violation: serialize_value before serialize_key\".into()),\n}","preventionTips":["Drive SerializeMap exclusively with serialize_entry(&k, &v) — atomic and panic-proof.","If you must call serialize_key + serialize_value separately, emit key first, then value, with no statement (especially 'continue'/'?'/'return') between them.","Prefer #[derive(Serialize)] for map-like types; only hand-write the SerializeMap loop when you need custom logic.","Grep your crate for 'serialize_value' and confirm each call site is immediately preceded by a serialize_key for the same entry.","Audit tagged/adjacently-tagged enums with custom Serialize payloads, since the content serializer (ser.rs:916) is what surfaces value-before-key bugs there."],"tags":["serde","rust","serialization","serializemap","panic","content-serializer"],"analyzedSha":"747814f7d5fbab872df3b02f070c165b91bde062","analyzedAt":"2026-08-06T01:20:43.063Z","schemaVersion":2}