serde-rs/json · error
serialize_value called before serialize_key
Error message
serialize_value called before serialize_key
What it means
This panic comes from serde_json's value Serializer (the one backing to_value) at src/value/ser.rs:426, inside SerializeMap::Map::serialize_value: it does next_key.take().expect("serialize_value called before serialize_key"). The serde SerializeMap contract requires each value to be preceded by exactly one serialize_key (or use serialize_entry, which does both). The inline comment at src/value/ser.rs:424-425 states this is treated as a bug in the program, not an expected failure, so it panics rather than returning an error. It can only fire when serializing into a serde_json::Value via this internal serializer.
Source
Thrown at src/value/ser.rs:426
Ok(())
}
#[cfg(feature = "arbitrary_precision")]
SerializeMap::Number { .. } => unreachable!(),
#[cfg(feature = "raw_value")]
SerializeMap::RawValue { .. } => unreachable!(),
}
}
fn serialize_value<T>(&mut self, value: &T) -> Result<()>
where
T: ?Sized + Serialize,
{
match self {
SerializeMap::Map { map, next_key } => {
let key = next_key.take();
// Panic because this indicates a bug in the program rather than an
// expected failure.
let key = key.expect("serialize_value called before serialize_key");
map.insert(key, tri!(to_value(value)));
Ok(())
}
#[cfg(feature = "arbitrary_precision")]
SerializeMap::Number { .. } => unreachable!(),
#[cfg(feature = "raw_value")]
SerializeMap::RawValue { .. } => unreachable!(),
}
}
fn end(self) -> Result<Value> {
match self {
SerializeMap::Map { map, .. } => Ok(Value::Object(map)),
#[cfg(feature = "arbitrary_precision")]
SerializeMap::Number { .. } => unreachable!(),
#[cfg(feature = "raw_value")]
SerializeMap::RawValue { .. } => unreachable!(),
}View on GitHub (pinned to afdf6fc672)
Solutions
- Drive the map with serialize_entry(k, v) instead of separate serialize_key/serialize_value calls — it cannot be misordered.
- If you must call them separately, guarantee strict serialize_key then serialize_value pairing in a loop and never call serialize_value twice for one key.
- Audit any custom Serialize impl or Serializer wrapper that touches serde_json::to_value / value::Serializer for a missing or conditional serialize_key before serialize_value.
- Add a unit test that runs to_value(&your_type) over an empty and a populated instance to surface the misordering before runtime.
- If you only need a JSON object, build a serde_json::Map/Value directly instead of implementing Serialize.
Example fix
// before — hand-rolled Serialize for a map type, driven via to_value
let mut m = serializer.serialize_map(Some(self.len()))?;
for (k, v) in self.iter() {
m.serialize_value(v)?; // BUG: no preceding serialize_key -> panics
}
m.end()
// after — use serialize_entry (or pair serialize_key + serialize_value)
let mut m = serializer.serialize_map(Some(self.len()))?;
for (k, v) in self.iter() {
m.serialize_entry(k, v)?;
}
m.end() Defensive patterns
Strategy: validation
Validate before calling
// Nothing to call before the API at runtime — the panic is a contract violation
// inside a Serialize impl driven by serde_json's value Serializer.
// Validate by round-tripping your type through to_value in a test:
#[cfg(test)]
fn check_serialization<T: serde::Serialize>(v: &T) -> Result<serde_json::Value, String> {
serde_json::to_value(v).map_err(|e| e.to_string())
}
// Call with both empty and populated instances; a misordered key/value pair
// will panic here in the test rather than in production. Type guard
// Not a type-guardable condition. The issue is call ordering inside Serialize, // not a value's type. There is no runtime type to narrow on.
Try / catch
// Prefer fixing the Serialize impl. catch_unwind only isolates untrusted impls:
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
serde_json::to_value(&maybe_buggy_type)
}));
match result {
Ok(Ok(value)) => { /* got a Value */ }
Ok(Err(e)) => { /* serialization Error, not the panic */ }
Err(_) => { /* serialize_value-before-serialize_key panic: audit the impl */ }
} Prevention
- Use serialize_entry(k, v) instead of separate serialize_key/serialize_value calls.
- Never call serialize_value without a preceding serialize_key on the same SerializeMap handle.
- Audit any Serializer wrapper/adapter that forwards serialize_value for a skipped serialize_key.
- Write a Serialize conformance test that drives to_value on empty and populated instances.
- Prefer building serde_json::Value/Map directly over hand-rolling Serialize for simple cases.
When it happens
Trigger: A hand-written serde::Serialize impl for a map-like type that drives the Serializer directly and calls serialize_value(&v) before any serialize_key(&k) on the same SerializeMap handle, or calls serialize_value twice. Triggered specifically through serde_json::to_value (or the public value::Serializer) on such a type, because the SerializeMap returned by serialize_struct/serialize_map is serde_json's SerializeMap::Map (src/value/ser.rs:264-269, 271-279). A wrapper Serializer/adapter that reorders or drops the serialize_key call before forwarding serialize_value also reproduces it.
Common situations: Developers writing a custom Serialize for a HashMap-wrapper, Multimap, or ordered-map type and forgetting the key/value pairing. A serde adapter (e.g. a flattening or renaming wrapper crate) that forwards serialize_value but conditionally skips serialize_key. Code copied from an example that used serialize_entry, later split into separate key/value calls with a bug. Rare with #[derive(Serialize)] — derived code always uses serialize_entry, so this usually points at hand-rolled serialization.
Related errors
AI-assisted analysis of serde-rs/json@afdf6fc672 (2026-08-08).
Data as JSON: /api/errors/78eb27cd737d2453.
Report an issue: GitHub.