core:serde
Serialization and deserialization framework.
Provides format-agnostic Serialize and Deserialize traits with a 17-method
data model optimized for JSON and CBOR. Format implementations (e.g., core:json)
implement Serializer and Deserializer to support specific wire formats.
use { Serialize, Deserialize, SerializeError, DeserializeError } from "core:serde";
Deriving Serialize / Deserialize
impl Serialize for T; / impl Deserialize for T; (empty body — the
compiler fills it in) is optional: a T: Serialize bound is satisfied
structurally once every field or case of T implements the trait — how
an anonymous struct, which has no name for a marker, becomes serializable.
Write the marker to force the impl with no bound present, or alongside
#[wire(...)] customization.
struct Point { x: i32, y: i32 } // no marker needed
let json = to_string(&Point { x: 1, y: 2 }); // Ok("{\"x\":1,\"y\":2}")
Field naming on the wire
By default the wire-form key is the Wado source field name verbatim
(identity): user_name stays "user_name", userId stays "userId".
The field name as written is the single source of truth. Override per
struct with #[wire(name_policy = "...")], or per field with
#[wire(name = "...")] (which takes precedence).
name_policy strategies: "camelCase", "snake_case", "PascalCase",
"SCREAMING_SNAKE_CASE", "kebab-case", "SCREAMING-KEBAB-CASE".
The same name / name_policy overrides apply to enum and variant
case names (default: the PascalCase name verbatim), e.g.
#[wire(name_policy = "kebab-case")] makes AddRemote serialize as
"add-remote".
// Default — wire keys are the field names as written
struct User {
user_name: String, // wire key: "user_name"
account_id: i64, // wire key: "account_id"
}
// Per-struct convention (e.g. camelCase for a JS-facing API)
#[wire(name_policy = "camelCase")]
struct Event {
created_at: String, // wire key: "createdAt"
event_type: String, // wire key: "eventType"
}
// Per-field override (wins over name_policy)
#[wire(name_policy = "kebab-case")]
struct Header {
content_type: String, // wire key: "content-type"
#[wire(name = "X-Trace-Id")]
trace_id: String, // wire key: "X-Trace-Id"
}
Missing fields on deserialization
A missing required field produces DeserializeError with kind
MissingField. A field with a default value (f: T = expr) is optional:
when absent it falls back to that expression. This is the single mechanism
for an optional field — for a zero-value fallback, write the zero literal
(= 0, = "", = false, = [], = null).
struct Config {
host: String, // required - error if missing
port: i32 = 0, // missing -> 0
timeout: i32 = 30, // missing -> 30
}
impl Deserialize for Config;
// {"host":"localhost"}
// -> Config { host: "localhost", port: 0, timeout: 30 }
Globals
pub global DEFAULT_MAX_DEPTH: i32
Default nesting limit. Unbounded nesting exhausts the stack and traps, which is unrecoverable unlike an error.
Functions
pub fn report_duplicate_field<K: Inspect>(policy: DuplicateFieldPolicy, key: &K) -> Result<(), DeserializeError>
Applies policy to a key the wire already wrote. Detecting the repeat is
the caller's job: a struct sees a filled slot, a map asks try_insert, a
set asks insert, whose element is its key.
pub fn deeper(depth: i32, max_depth: i32, offset: i64) -> Result<i32, DeserializeError>
One nesting level deeper, or DepthLimitExceeded at the limit. Threaded as
a value, so there is nothing to decrement and a format cannot leave the
count unbalanced; each picks its own max_depth and container boundaries.
pub fn apply_case<S: AsStrSlice>(style: CaseStyle, s: S) -> String
Apply a CaseStyle to an identifier. Identity returns s unchanged.
Word boundaries follow heck: _/-/space delimit and are dropped; a
lower/digit → upper transition starts a word; inside an uppercase run, an
uppercase directly before a lowercase starts one. Digits attach to the
current word.
pub fn wire_name<M: Member>(m: &M, policy: CaseStyle) -> String
Resolve a member's wire name: its #[wire(name)] override wins,
else the type's name_policy applies, else identity.
Traits
pub trait SerializeSeq with ()
fn element<T: Serialize>(&mut self, value: &T) -> Result<(), SerializeError>
#[compiler_item("serialize_seq_element")]
fn end(&mut self) -> Result<(), SerializeError>
#[compiler_item("serialize_seq_end")]
pub trait SerializeMap with ()
fn key<T: Serialize>(&mut self, key: &T) -> Result<(), SerializeError>
fn value<T: Serialize>(&mut self, value: &T) -> Result<(), SerializeError>
fn end(&mut self) -> Result<(), SerializeError>
pub trait SerializeStruct with ()
fn field<T: Serialize, S: AsStrSlice>(&mut self, name: S, value: &T) -> Result<(), SerializeError>
#[compiler_item("serialize_struct_field")]
fn end(&mut self) -> Result<(), SerializeError>
#[compiler_item("serialize_struct_end")]
pub trait SerializeVariant with ()
fn payload<T: Serialize>(&mut self, value: &T) -> Result<(), SerializeError>
#[compiler_item("serialize_variant_payload")]
fn end(&mut self) -> Result<(), SerializeError>
#[compiler_item("serialize_variant_end")]
pub trait Serializer with ()
fn serialize_i32(&mut self, v: i32) -> Result<(), SerializeError>
fn serialize_i64(&mut self, v: i64) -> Result<(), SerializeError>
fn serialize_u32(&mut self, v: u32) -> Result<(), SerializeError>
fn serialize_u64(&mut self, v: u64) -> Result<(), SerializeError>
fn serialize_i128(&mut self, v: i128) -> Result<(), SerializeError>
fn serialize_u128(&mut self, v: u128) -> Result<(), SerializeError>
fn serialize_f32(&mut self, v: f32) -> Result<(), SerializeError>
fn serialize_f64(&mut self, v: f64) -> Result<(), SerializeError>
fn serialize_bool(&mut self, v: bool) -> Result<(), SerializeError>
fn serialize_char(&mut self, v: char) -> Result<(), SerializeError>
fn serialize_string<S: AsStrSlice>(&mut self, v: S) -> Result<(), SerializeError>
fn is_human_readable(&self) -> bool
Whether the output is meant to be read by people. Binary formats override it to false so a value can pick a compact representation.
fn serialize_tag(&mut self, _tag: u64) -> Result<(), SerializeError>
Tag the next value with a CBOR semantic tag (major type 6, RFC 8949 §3.4). Formats with no tag concept ignore it; the default no-op means only tag-aware serializers (CBOR) need to implement it.
fn serialize_bytes(&mut self, v: ByteSlice) -> Result<(), SerializeError>
Serialize a byte string. Self-describing binary formats (CBOR) emit a
native byte string; JSON emits base64. The default falls back to a
sequence of u8 (the historical List<u8> behaviour), so a format
with no distinct byte-string form keeps working unchanged.
fn serialize_null(&mut self) -> Result<(), SerializeError>
fn begin_seq(&mut self, len: i32) -> Result<Self::SeqSerializer, SerializeError>
#[compiler_item("serializer_begin_seq")]
fn begin_map(&mut self, len: i32) -> Result<Self::MapSerializer, SerializeError>
fn begin_struct<S: AsStrSlice>(&mut self, name: S, fields: i32) -> Result<Self::StructSerializer, SerializeError>
#[compiler_item("serializer_begin_struct")]
fn serialize_unit_variant<S: AsStrSlice, S1: AsStrSlice>(&mut self, type_name: S, variant_name: S1, disc: i32) -> Result<(), SerializeError>
#[compiler_item("serializer_serialize_unit_variant")]
fn begin_variant<S: AsStrSlice, S1: AsStrSlice>(&mut self, type_name: S, variant_name: S1, disc: i32) -> Result<Self::VariantSerializer, SerializeError>
#[compiler_item("serializer_begin_variant")]
pub trait Serialize with ()
fn serialize<S: Serializer>(&self, s: &mut S) -> Result<(), SerializeError>
pub trait DeserializeSeq with ()
fn next_element<T: Deserialize>(&mut self) -> Result<Option<T>, DeserializeError>
#[compiler_item("deserialize_seq_next_element")]
fn end(&mut self) -> Result<(), DeserializeError>
#[compiler_item("deserialize_seq_end")]
pub trait DeserializeMap with ()
fn next_key<K: Deserialize>(&mut self) -> Result<Option<K>, DeserializeError>
The next entry's key, or null past the last one. A text format spells
every key as a string, so a key there is a scalar parsed back as K.
fn next_value<V: Deserialize>(&mut self) -> Result<V, DeserializeError>
fn end(&mut self) -> Result<(), DeserializeError>
pub trait FieldSchema with ()
Static, per-type, format-agnostic field-name → index mapping.
The struct-deserialize synthesiser implements FieldSchema for every
deserializable struct. Its lookup walks a decision tree over the key's
bytes: the length at the root, then the byte that best tells the remaining
field names apart. A key costs one walk down that tree, not one test per
declared field. Self-describing formats invoke it from
DeserializeStruct::next_field with the key's bytes; non-self-describing
formats ignore the schema and stay positional.
The key is a byte view (Slice<u8>, i.e. ByteSlice), not a
String: deserializer input is bytes (UTF-8 for JSON, a byte string for
CBOR), so a bytes key avoids a String round-trip and is shared identically
by every format.
lookup is a static method resolved at monomorphization, so the call is
direct and inlinable — no closure value, no per-decode allocation.
A field marked #[wire(positional)] is ordinal, not nominal: lookup
omits it (it is never matched by name) and positional_at enumerates the
positional fields in declaration order. Name-only and sequence-only formats
ignore positional_at and stay backward compatible; core:args consults it
to bind bare tokens to positional fields.
fn lookup(key: ByteSlice) -> Option<i32>
fn positional_at(rank: i32) -> Option<i32>
Field index of the rank-th #[wire(positional)] field (in
declaration order), or null when rank is out of range. Returns
null for every rank when the type has no positional fields.
pub trait DeserializeStruct with ()
fn next_field<S: FieldSchema>(&mut self) -> Result<Option<i32>, DeserializeError>
#[compiler_item("deserialize_struct_next_field")]
fn value<T: Deserialize>(&mut self) -> Result<T, DeserializeError>
#[compiler_item("deserialize_struct_value")]
fn skip(&mut self) -> Result<(), DeserializeError>
#[compiler_item("deserialize_struct_skip")]
fn end(&mut self) -> Result<(), DeserializeError>
#[compiler_item("deserialize_struct_end")]
pub trait DeserializeVariant with ()
fn variant_name(&mut self) -> Result<String, DeserializeError>
#[compiler_item("deserialize_variant_variant_name")]
fn disc(&mut self) -> Result<i32, DeserializeError>
#[compiler_item("deserialize_variant_disc")]
fn payload<T: Deserialize>(&mut self) -> Result<T, DeserializeError>
#[compiler_item("deserialize_variant_payload")]
fn is_unit(&mut self) -> Result<bool, DeserializeError>
fn end(&mut self) -> Result<(), DeserializeError>
#[compiler_item("deserialize_variant_end")]
pub trait Visitor with ()
fn visit_null(&mut self) -> Result<Self::Value, DeserializeError>
fn visit_bool(&mut self, v: bool) -> Result<Self::Value, DeserializeError>
fn visit_f64(&mut self, v: f64) -> Result<Self::Value, DeserializeError>
fn visit_i64(&mut self, v: i64) -> Result<Self::Value, DeserializeError>
Integer visits. Self-describing binary formats (CBOR) call these to
preserve full integer precision; the defaults widen to f64 so a
visitor that only cares about JSON-style numbers keeps working.
fn visit_u64(&mut self, v: u64) -> Result<Self::Value, DeserializeError>
fn visit_i128(&mut self, v: i128) -> Result<Self::Value, DeserializeError>
fn visit_u128(&mut self, v: u128) -> Result<Self::Value, DeserializeError>
fn visit_string<S: AsStrSlice>(&mut self, v: S) -> Result<Self::Value, DeserializeError>
fn visit_bytes(&mut self, _v: ByteList) -> Result<Self::Value, DeserializeError>
Byte-string visit (CBOR major type 2; JSON base64). Defaults to an
UnexpectedType error so text-only visitors are unaffected.
fn visit_seq<A: DeserializeSeq>(&mut self, seq: &mut A) -> Result<Self::Value, DeserializeError>
fn visit_map<A: DeserializeMap>(&mut self, map: &mut A) -> Result<Self::Value, DeserializeError>
fn visit_unknown(&mut self, _raw: ByteList) -> Result<Self::Value, DeserializeError>
Opaque passthrough for a format item the value model cannot represent (e.g. an unassigned CBOR simple value or an uninterpreted tag). Only self-describing binary formats call this; defaults to an error.
fn visit_undefined(&mut self) -> Result<Self::Value, DeserializeError>
The CBOR undefined simple value (major 7, value 23). Distinct from
null: a dynamic value model (core:value) keeps its own Undefined
arm. The default collapses it to null so visitors that do not model
the distinction (e.g. JSON) keep working.
pub trait Deserializer with ()
fn deserialize_i32(&mut self) -> Result<i32, DeserializeError>
fn deserialize_i64(&mut self) -> Result<i64, DeserializeError>
fn deserialize_u32(&mut self) -> Result<u32, DeserializeError>
fn deserialize_u64(&mut self) -> Result<u64, DeserializeError>
fn deserialize_i128(&mut self) -> Result<i128, DeserializeError>
fn deserialize_u128(&mut self) -> Result<u128, DeserializeError>
fn deserialize_f32(&mut self) -> Result<f32, DeserializeError>
fn deserialize_f64(&mut self) -> Result<f64, DeserializeError>
fn deserialize_f16(&mut self) -> Result<f16, DeserializeError>
Rounds what the input spells once, to nearest, into a half. Reading an
f32 and narrowing it would round twice.
fn deserialize_bf16(&mut self) -> Result<bf16, DeserializeError>
deserialize_f16, into a bf16.
fn deserialize_bool(&mut self) -> Result<bool, DeserializeError>
fn deserialize_char(&mut self) -> Result<char, DeserializeError>
fn deserialize_string(&mut self) -> Result<String, DeserializeError>
fn deserialize_bytes(&mut self) -> Result<ByteList, DeserializeError>
Deserialize a byte string. Self-describing binary formats read a native
byte string; JSON reads base64. The default reads a sequence of u8.
fn is_null(&mut self) -> Result<bool, DeserializeError>
fn on_duplicate_field(&self) -> DuplicateFieldPolicy
What this format does when the wire repeats a field or key.
fn begin_seq(&mut self) -> Result<Self::SeqAccess, DeserializeError>
#[compiler_item("deserializer_begin_seq")]
fn begin_map(&mut self) -> Result<Self::MapAccess, DeserializeError>
fn begin_struct<S: AsStrSlice>(&mut self, name: S, num_fields: i32) -> Result<Self::StructAccess, DeserializeError>
#[compiler_item("deserializer_begin_struct")]
fn begin_variant<S: AsStrSlice>(&mut self, type_name: S, num_cases: i32) -> Result<Self::VariantAccess, DeserializeError>
#[compiler_item("deserializer_begin_variant")]
fn deserialize_any<V: Visitor>(&mut self, visitor: &mut V) -> Result<V::Value, DeserializeError>
pub trait Deserialize with ()
fn deserialize<D: Deserializer>(d: &mut D) -> Result<Self, DeserializeError>
fn deserialize_next_element_from_seq<S: DeserializeSeq>(seq: &mut S) -> Result<Self, DeserializeError>
Deserialize the next element from a sequence access. Used by variadic tuple deserialization via type pack expansion.
Structs
pub struct SerializeError
kind: SerializeErrorKind
message: String
pub fn unsupported_value<S: AsStrSlice>(msg: S) -> SerializeError
A value the format has no way to write.
pub fn non_scalar_key() -> SerializeError
A map key whose type is not a scalar, which a text format has no one string to spell as.
impl Display for SerializeError
fn fmt(&self, f: &mut Formatter)
pub struct DeserializeError
kind: DeserializeErrorKind
message: String
offset: i64
Byte offset into the input, or -1 where the failure has no position —
a whole-value rejection, or a format that counts nothing.
pub fn unexpected_type<S: AsStrSlice>(msg: S, offset: i64 = -1) -> DeserializeError
pub fn missing_field<S: AsStrSlice>(name: S) -> DeserializeError
pub fn invalid_value<S: AsStrSlice>(msg: S, offset: i64 = -1) -> DeserializeError
pub fn malformed<S: AsStrSlice>(msg: S, offset: i64 = -1) -> DeserializeError
pub fn eof(offset: i64) -> DeserializeError
pub fn trailing(offset: i64) -> DeserializeError
pub fn overflow<S: AsStrSlice>(msg: S, offset: i64 = -1) -> DeserializeError
pub fn depth_limit<S: AsStrSlice>(msg: S, offset: i64 = -1) -> DeserializeError
pub fn duplicate_field<K: Inspect>(key: &K) -> DeserializeError
A repeated field or key, named as Inspect spells it: a string key is
quoted, so it reads apart from a number.
pub fn non_scalar_key(offset: i64) -> DeserializeError
SerializeError::non_scalar_key, for a key being read.
impl Display for DeserializeError
fn fmt(&self, f: &mut Formatter)
pub struct IgnoredAny
A value of any shape, read and thrown away.
It is how a schema requires a member's absence: an Option<T> field
cannot, because it reads an explicit null as the member never sent.
impl Deserialize for IgnoredAny
fn deserialize<D: Deserializer>(d: &mut D) -> Result<IgnoredAny, DeserializeError>
pub struct NoCompound
The compound access of a serializer or deserializer that has none, such as
a map key's. The private field keeps it unbuilt outside this module, so the
begin_* that would hand one out always fails first.
Fields are private.
impl SerializeSeq for NoCompound
fn element<T: Serialize>(&mut self, value: &T) -> Result<(), SerializeError>
fn end(&mut self) -> Result<(), SerializeError>
impl SerializeMap for NoCompound
fn key<T: Serialize>(&mut self, key: &T) -> Result<(), SerializeError>
fn value<T: Serialize>(&mut self, value: &T) -> Result<(), SerializeError>
fn end(&mut self) -> Result<(), SerializeError>
impl SerializeStruct for NoCompound
fn field<T: Serialize, S: AsStrSlice>(&mut self, name: S, value: &T) -> Result<(), SerializeError>
fn end(&mut self) -> Result<(), SerializeError>
impl SerializeVariant for NoCompound
fn payload<T: Serialize>(&mut self, value: &T) -> Result<(), SerializeError>
fn end(&mut self) -> Result<(), SerializeError>
impl DeserializeSeq for NoCompound
fn next_element<T: Deserialize>(&mut self) -> Result<Option<T>, DeserializeError>
fn end(&mut self) -> Result<(), DeserializeError>
impl DeserializeMap for NoCompound
fn next_key<K: Deserialize>(&mut self) -> Result<Option<K>, DeserializeError>
fn next_value<V: Deserialize>(&mut self) -> Result<V, DeserializeError>
fn end(&mut self) -> Result<(), DeserializeError>
impl DeserializeStruct for NoCompound
fn next_field<S: FieldSchema>(&mut self) -> Result<Option<i32>, DeserializeError>
fn value<T: Deserialize>(&mut self) -> Result<T, DeserializeError>
fn skip(&mut self) -> Result<(), DeserializeError>
fn end(&mut self) -> Result<(), DeserializeError>
impl DeserializeVariant for NoCompound
fn variant_name(&mut self) -> Result<String, DeserializeError>
fn disc(&mut self) -> Result<i32, DeserializeError>
fn payload<T: Deserialize>(&mut self) -> Result<T, DeserializeError>
fn is_unit(&mut self) -> Result<bool, DeserializeError>
fn end(&mut self) -> Result<(), DeserializeError>
pub struct TextKeySerializer
Spells a map key as the text TextKeyDeserializer reads back: a string as
itself, any other scalar as its Display.
out: &mut String
impl Serializer for TextKeySerializer
fn serialize_i32(&mut self, v: i32) -> Result<(), SerializeError>
fn serialize_i64(&mut self, v: i64) -> Result<(), SerializeError>
fn serialize_u32(&mut self, v: u32) -> Result<(), SerializeError>
fn serialize_u64(&mut self, v: u64) -> Result<(), SerializeError>
fn serialize_i128(&mut self, v: i128) -> Result<(), SerializeError>
fn serialize_u128(&mut self, v: u128) -> Result<(), SerializeError>
fn serialize_f32(&mut self, v: f32) -> Result<(), SerializeError>
fn serialize_f64(&mut self, v: f64) -> Result<(), SerializeError>
fn serialize_bool(&mut self, v: bool) -> Result<(), SerializeError>
fn serialize_char(&mut self, v: char) -> Result<(), SerializeError>
fn serialize_string<S: AsStrSlice>(&mut self, v: S) -> Result<(), SerializeError>
fn serialize_null(&mut self) -> Result<(), SerializeError>
fn begin_seq(&mut self, len: i32) -> Result<NoCompound, SerializeError>
fn begin_map(&mut self, len: i32) -> Result<NoCompound, SerializeError>
fn begin_struct<S: AsStrSlice>(&mut self, name: S, fields: i32) -> Result<NoCompound, SerializeError>
fn serialize_unit_variant<S: AsStrSlice, S1: AsStrSlice>(&mut self, type_name: S, variant_name: S1, disc: i32) -> Result<(), SerializeError>
fn begin_variant<S: AsStrSlice, S1: AsStrSlice>(&mut self, type_name: S, variant_name: S1, disc: i32) -> Result<NoCompound, SerializeError>
pub struct TextKeyDeserializer
Reads a map key a text format spelled as a string, parsed as the scalar the key type asks for.
key: String
offset: i64
Where the key starts in the input, or -1 where it has no position.
impl Deserializer for TextKeyDeserializer
fn deserialize_i32(&mut self) -> Result<i32, DeserializeError>
fn deserialize_i64(&mut self) -> Result<i64, DeserializeError>
fn deserialize_u32(&mut self) -> Result<u32, DeserializeError>
fn deserialize_u64(&mut self) -> Result<u64, DeserializeError>
fn deserialize_i128(&mut self) -> Result<i128, DeserializeError>
fn deserialize_u128(&mut self) -> Result<u128, DeserializeError>
fn deserialize_f32(&mut self) -> Result<f32, DeserializeError>
fn deserialize_f64(&mut self) -> Result<f64, DeserializeError>
fn deserialize_f16(&mut self) -> Result<f16, DeserializeError>
fn deserialize_bf16(&mut self) -> Result<bf16, DeserializeError>
fn deserialize_bool(&mut self) -> Result<bool, DeserializeError>
fn deserialize_char(&mut self) -> Result<char, DeserializeError>
fn deserialize_string(&mut self) -> Result<String, DeserializeError>
fn is_null(&mut self) -> Result<bool, DeserializeError>
fn begin_seq(&mut self) -> Result<NoCompound, DeserializeError>
fn begin_map(&mut self) -> Result<NoCompound, DeserializeError>
fn begin_struct<S: AsStrSlice>(&mut self, name: S, num_fields: i32) -> Result<NoCompound, DeserializeError>
fn begin_variant<S: AsStrSlice>(&mut self, type_name: S, num_cases: i32) -> Result<NoCompound, DeserializeError>
fn deserialize_any<V: Visitor>(&mut self, visitor: &mut V) -> Result<V::Value, DeserializeError>
Enums
pub enum SerializeErrorKind
UnsupportedValue
Custom
pub enum DeserializeErrorKind
UnexpectedType
MissingField
UnknownVariant
DuplicateField
InvalidValue
Overflow
MalformedInput
TrailingData
Eof
Custom
DepthLimitExceeded
Nesting ran past the deserializer's depth limit. A resource bound, not a spec violation: the input may be perfectly well-formed.
pub enum DuplicateFieldPolicy
What to do when the wire repeats a field or key. A repeat is how two
readers of the same bytes are made to disagree, so rejecting is the
default. Warn and PassThru both keep the last value, and a map key
or set element as first read.
