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

  1. 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.
  2. Prefer map.serialize_entry(&k, &v)? which emits key and value atomically and cannot trigger this panic.
  3. Audit any 'continue'/'?'/'return' between serialize_key and serialize_value that could skip one half of the pair.
  4. 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

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.