serde-rs/serde · critical
serialize_value called before serialize_key
Error message
serialize_value called before serialize_key
What it means
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.
Source
Thrown at serde/src/private/ser.rs:916
type Error = E;
fn serialize_key<T>(&mut self, key: &T) -> Result<(), E>
where
T: ?Sized + Serialize,
{
let key = tri!(key.serialize(ContentSerializer::<E>::new()));
self.key = Some(key);
Ok(())
}
fn serialize_value<T>(&mut self, value: &T) -> Result<(), E>
where
T: ?Sized + Serialize,
{
let key = self
.key
.take()
.expect("serialize_value called before serialize_key");
let value = tri!(value.serialize(ContentSerializer::<E>::new()));
self.entries.push((key, value));
Ok(())
}
fn end(self) -> Result<Content, E> {
Ok(Content::Map(self.entries))
}
fn serialize_entry<K, V>(&mut self, key: &K, value: &V) -> Result<(), E>
where
K: ?Sized + Serialize,
V: ?Sized + Serialize,
{
let key = tri!(key.serialize(ContentSerializer::<E>::new()));
let value = tri!(value.serialize(ContentSerializer::<E>::new()));
self.entries.push((key, value));
Ok(())View on GitHub (pinned to 747814f7d5)
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.
Example fix
// before — broken: value emitted before key
let mut m = serializer.serialize_map(Some(1))?;
m.serialize_value(&42)?; // PANIC at ser.rs:916
m.serialize_key("answer")?;
m.end()
// after — use serialize_entry (or key-then-value)
let mut m = serializer.serialize_map(Some(1))?;
m.serialize_entry("answer", &42)?; // key+value atomically
m.end() Defensive patterns
Strategy: validation
Validate before calling
// Validate at authoring time: drive SerializeMap with serialize_entry only,
// which makes the 'value before key' panic structurally impossible.
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: serde::Serializer,
{
let mut m = serializer.serialize_map(Some(self.inner.len()))?;
for (k, v) in &self.inner {
m.serialize_entry(k, v)?; // atomic key+value — cannot panic
}
m.end()
} Try / catch
// Last-resort only; catches the ser.rs:916 panic at a boundary. The real fix is
// to emit key before value (or use serialize_entry).
use std::panic::{catch_unwind, AssertUnwindSafe};
let res = catch_unwind(AssertUnwindSafe(|| value.serialize(&mut serializer)));
match res {
Ok(Ok(v)) => Ok(v),
Ok(Err(e)) => Err(e.into()),
Err(_) => Err("serde SerializeMap protocol violation: serialize_value before serialize_key".into()),
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
AI-assisted analysis of serde-rs/serde@747814f7d5 (2026-08-06).
Data as JSON: /data/errors/1ca774dc2e1ca396.json.
Report an issue: GitHub.