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
- 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.
- 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().
- Delete any hardcoded copy of the TOKEN string; depend on serde_json's public RawValue instead of mirroring internals.
- Reproduce with serde_json::to_value on a real RawValue (raw_value enabled) to confirm the happy path, then remove the manual mirror.
- 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
- Never hardcode "$serde_json::private::RawValue" as a struct name; use serde_json::value::RawValue.
- If you implement a Serializer honoring the protocol, always emit the field before end().
- Pin and audit serde_json versions across the workspace.
- Test RawValue round-trips through to_value with raw_value enabled.
- Audit dependencies for vendored copies of the private TOKEN string.
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.