serde-rs/json · error

raw value was not emitted

Error message

raw value was not emitted

What it means

With the raw_value feature, serde_json::RawValue serializes itself via the same private struct protocol as Number: RawValue::serialize calls serialize_struct("$serde_json::private::RawValue", 1) then serialize_field("$serde_json::private::RawValue", &self.json). The value Serializer routes that name to SerializeMap::RawValue { out_value: None } (src/value/ser.rs:276), and the field sets out_value = Some(...). The panic at src/value/ser.rs:690 fires in end() when out_value is still None — the required raw-JSON field was never emitted. The constant TOKEN lives at src/raw.rs:342. Like error 2, this guards an internal round-trip invariant and is not reachable through normal public usage.

Source

Thrown at src/value/ser.rs:690

                    *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)));
        Ok(())
    }

    fn end(self) -> Result<Value> {

View on GitHub (pinned to afdf6fc672)

Solutions

  1. Stop reconstructing the private $serde_json::private::RawValue protocol; use serde_json::value::RawValue (and its from_str / serialize) so the field is always emitted correctly.
  2. If you implement a Serializer that must honor the protocol, guarantee serialize_struct with that name is followed by exactly one serialize_field with the identical key before end().
  3. Delete any hardcoded copy of the TOKEN string; depend on serde_json's public RawValue instead of mirroring internals.
  4. Reproduce with serde_json::to_value on a real RawValue (raw_value enabled) to confirm the happy path, then remove the manual mirror.
  5. Pin and audit serde_json versions across the workspace so the RawValue TOKEN contract does not silently drift.

Example fix

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

// after — use the public RawValue API; it emits the field itself
use serde_json::value::RawValue;
let raw: &RawValue = serde_json::from_str("{\"x\":1}")?;
raw.serialize(serializer)
Defensive patterns

Strategy: validation

Validate before calling

// No runtime pre-check exists; the panic is internal to the
// $serde_json::private::RawValue struct protocol. Accept raw JSON only via
// the public RawValue type so the field is always emitted:
fn make_raw(json: &str) -> Result<Box<serde_json::value::RawValue>, serde_json::Error> {
    serde_json::value::RawValue::from_string(json.to_owned())
}

Type guard

// Not applicable: the panic is keyed on a private struct name inside the
// value Serializer, not a caller-narrowable type.

Try / catch

// Fix the producer; catch_unwind only isolates untrusted code:
let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
    serde_json::to_value(&value_with_custom_serialize)
}));
if res.is_err() {
    // raw value was not emitted — stop reconstructing the TOKEN protocol
}

Prevention

When it happens

Trigger: Code that manually calls serializer.serialize_struct("$serde_json::private::RawValue", 1) against the to_value / value::Serializer and then calls end() without a matching serialize_field("$serde_json::private::RawValue", ...) (key equality checked at src/value/ser.rs:671). A Serialize wrapper/adapter that forwards the struct name but drops, reorders, or renames the field. Version skew where a downstream crate hardcodes a TOKEN string that no longer matches crate::raw::TOKEN. Not reachable through serde_json::to_value/to_string on a real RawValue, because RawValue::serialize always emits the field.

Common situations: A crate implementing a JSON-passthrough or buffered serializer that mirrors serde_json's RawValue magic struct instead of using serde_json::value::RawValue. Copying the constant $serde_json::private::RawValue from serde_json internals/docs into a custom Serialize impl. Upgrading serde_json and finding a hand-maintained mirror diverges from the canonical TOKEN. Building a generic Serializer test harness that feeds arbitrary struct names, one of which collides with the RawValue TOKEN.

Related errors


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