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

  1. Drive the map with serialize_entry(k, v) instead of separate serialize_key/serialize_value calls — it cannot be misordered.
  2. 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.
  3. 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.
  4. Add a unit test that runs to_value(&your_type) over an empty and a populated instance to surface the misordering before runtime.
  5. 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

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.