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
#[serde(...)] 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 #[serde(rename_all = "...")], or per field with
#[serde(rename = "...")] (which takes precedence).
rename_all strategies: "camelCase", "snake_case", "PascalCase",
"SCREAMING_SNAKE_CASE", "kebab-case", "SCREAMING-KEBAB-CASE".
The same rename / rename_all overrides apply to enum and variant
case names (default: the PascalCase name verbatim), e.g.
#[serde(rename_all = "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)
#[serde(rename_all = "camelCase")]
struct Event {
created_at: String, // wire key: "createdAt"
event_type: String, // wire key: "eventType"
}
// Per-field override (wins over rename_all)
#[serde(rename_all = "kebab-case")]
struct Header {
content_type: String, // wire key: "content-type"
#[serde(rename = "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 }
Traits
pub trait SerializeSeq
fn element<T: Serialize>(&mut self, value: &T) -> Result<(), SerializeError>
fn end(&mut self) -> Result<(), SerializeError>
pub trait SerializeMap
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
fn field<T: Serialize>(&mut self, name: &String, value: &T) -> Result<(), SerializeError>
fn end(&mut self) -> Result<(), SerializeError>
pub trait SerializeVariant
fn payload<T: Serialize>(&mut self, value: &T) -> Result<(), SerializeError>
fn end(&mut self) -> Result<(), SerializeError>
pub trait Serializer
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(&mut self, v: &String) -> Result<(), SerializeError>
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> with stores[self]
fn begin_map(&mut self, len: i32) -> Result<Self::MapSerializer, SerializeError> with stores[self]
fn begin_struct(&mut self, name: &String, fields: i32) -> Result<Self::StructSerializer, SerializeError> with stores[self]
fn serialize_unit_variant(&mut self, type_name: &String, variant_name: &String, disc: i32) -> Result<(), SerializeError>
fn begin_variant(&mut self, type_name: &String, variant_name: &String, disc: i32) -> Result<Self::VariantSerializer, SerializeError> with stores[self]
pub trait Serialize
fn serialize<S: Serializer>(&self, s: &mut S) -> Result<(), SerializeError>
pub trait DeserializeSeq
fn next_element<T: Deserialize>(&mut self) -> Result<Option<T>, DeserializeError>
fn end(&mut self) -> Result<(), DeserializeError>
pub trait DeserializeMap
fn next_key_string(&mut self) -> Result<Option<String>, DeserializeError>
fn next_value<V: Deserialize>(&mut self) -> Result<V, DeserializeError>
fn end(&mut self) -> Result<(), DeserializeError>
pub trait FieldSchema
Static, per-type, format-agnostic field-name → index mapping.
The struct-deserialize synthesiser implements FieldSchema for every
deserializable struct, generating a lookup that byte-compares the wire
key against the wire-form field names. 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 borrowed byte view (ArraySlice<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 #[serde(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: ArraySlice<u8>) -> Option<i32>
fn positional_at(rank: i32) -> Option<i32>
Field index of the rank-th #[serde(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
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>
pub trait DeserializeVariant
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 trait Visitor
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(&mut self, v: String) -> 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
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_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 begin_seq(&mut self) -> Result<Self::SeqAccess, DeserializeError> with stores[self]
fn begin_map(&mut self) -> Result<Self::MapAccess, DeserializeError> with stores[self]
fn begin_struct(&mut self, name: &String, num_fields: i32) -> Result<Self::StructAccess, DeserializeError> with stores[self]
fn begin_variant(&mut self, type_name: &String, num_cases: i32) -> Result<Self::VariantAccess, DeserializeError> with stores[self]
fn deserialize_any<V: Visitor>(&mut self, visitor: &mut V) -> Result<V::Value, DeserializeError>
pub trait Deserialize
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.
