serde-rs/json · error

number value was not emitted

Error message

number value was not emitted

What it means

With the arbitrary_precision feature, serde_json::Number serializes itself via a private struct protocol: Number::serialize (src/number.rs:392) calls serialize_struct("$serde_json::private::Number", 1) then serialize_field("$serde_json::private::Number", &self.n). The value Serializer routes that struct name to SerializeMap::Number { out_value: None } (src/value/ser.rs:274), and serialize_field sets out_value = Some(...). The panic at src/value/ser.rs:686 fires in end() when out_value is still None — i.e. the required single field was never emitted. This is an internal invariant guarding serde_json's own Number round-trip; the constant TOKEN is defined at src/number.rs:18.

Source

Thrown at src/value/ser.rs:686

            }
            #[cfg(feature = "raw_value")]
            SerializeMap::RawValue { out_value } => {
                if key == crate::raw::TOKEN {
                    *out_value = Some(tri!(value.serialize(RawValueEmitter)));
                    Ok(())
                } else {
                    Err(invalid_raw_value())
                }
            }
        }
    }

    fn end(self) -> Result<Value> {
        match self {
            SerializeMap::Map { .. } => serde::ser::SerializeMap::end(self),
            #[cfg(feature = "arbitrary_precision")]
            SerializeMap::Number { out_value, .. } => {
                Ok(out_value.expect("number value was not emitted"))
            }
            #[cfg(feature = "raw_value")]
            SerializeMap::RawValue { out_value, .. } => {
                Ok(out_value.expect("raw value was not emitted"))
            }
        }
    }
}

impl serde::ser::SerializeStructVariant for SerializeStructVariant {
    type Ok = Value;
    type Error = Error;

    fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<()>
    where
        T: ?Sized + Serialize,
    {
        self.map.insert(String::from(key), tri!(to_value(value)));

View on GitHub (pinned to afdf6fc672)

Solutions

  1. Stop reconstructing the private $serde_json::private::Number protocol; use the public serde_json::Number API (Number::from_str / from_i64 / from_f64 / serialize) so the field is always emitted correctly.
  2. If you are writing a Serializer that must honor the protocol, ensure serialize_struct with that name is always followed by exactly one serialize_field with the same key before end().
  3. Remove any hardcoded copy of the TOKEN string; if you must reference it, keep it in lockstep with the serde_json version you depend on, or avoid the protocol entirely.
  4. Reproduce in isolation with serde_json::to_value on your type (arbitrary_precision enabled) to confirm the field path is taken, then drop the manual mirror.
  5. Pin and audit serde_json versions across your workspace so the TOKEN contract does not silently drift.

Example fix

// before — reconstructing serde_json's private Number protocol
let name = "$serde_json::private::Number";
let mut s = serializer.serialize_struct(name, 1)?;
// forgot the field, or used a wrong key -> end() panics: number value was not emitted
s.end()

// after — use the public API; Number emits the field itself
let n: serde_json::Number = serde_json::Number::from_str("123456789012345678901234567890")
    .map_err(serde::ser::Error::custom)?;
n.serialize(serializer)
Defensive patterns

Strategy: validation

Validate before calling

// No pre-call runtime check prevents this; it fires inside end() for the
// private $serde_json::private::Number struct protocol. The only safe input
// is a real serde_json::Number. If you must accept serialized numbers, build
// them through the public API:
fn make_number(s: &str) -> Result<serde_json::Number, serde_json::Error> {
    // validates the representation used by arbitrary_precision round-trips
    serde_json::Number::from_str(s).ok_or_else(|| {
        serde::de::Error::custom("invalid number literal")
    })
}

Type guard

// Not applicable: the panic is an internal-contract violation keyed on a
// private struct name, not a type you can narrow at the call site.

Try / catch

// Fix the producer; do not catch. If isolating an untrusted serializer:
let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
    serde_json::to_value(&value_with_custom_serialize)
}));
if res.is_err() {
    // number value was not emitted — stop reconstructing the TOKEN protocol
}

Prevention

When it happens

Trigger: Code that manually calls serializer.serialize_struct("$serde_json::private::Number", 1) (copying serde_json's private TOKEN) against the to_value / value::Serializer and then calls end() without a matching serialize_field("$serde_json::private::Number", ...), or with the field key misspelled (the equality check is at src/value/ser.rs:662). A generic Serialize wrapper/adapter that forwards the struct name but drops or renames the field. A serde version skew where a downstream crate hardcodes an old/new TOKEN string that no longer matches crate::number::TOKEN. Not reachable through normal public API usage (to_value, to_string, derived Serialize) because Number::serialize always emits the field.

Common situations: A crate that tries to interoperate with serde_json's arbitrary_precision representation by reconstructing the magic struct instead of using serde_json::Number. Copy-pasting the private constant $serde_json::private::Number from serde_json source or docs. Upgrading serde_json across a version that changed/clarified the TOKEN and finding a hand-maintained mirror now diverges. Fuzzing or property-testing a custom Serializer against serde_json's value Serializer with arbitrary struct names that happen to collide.

Related errors


AI-assisted analysis of serde-rs/json@afdf6fc672 (2026-08-08). Data as JSON: /api/errors/e8f13afcca202c9f. Report an issue: GitHub.