#Serde
Status: standard library specification draft.
This chapter defines the part of std.serde that lib/std writes in
ordinary hd over the language tier:
- the data model: what each
Serializercall writes, and what eachDeserializercall reads; - what a derived
SerializeorDeserializecalls, through the traits' templates; - the standard implementations of both traits.
The language tier keeps the module's declarations and what opting in means
(Serialization), since the host
boundary reads the implementation's output, not the field tree
(module.boundary.out).
#Data Model
A format sees one value as a tree of calls on its writer:
use std.serde.{Serialize, Serializer}
fn write_pair[W < Serializer](out: &mut W, name: string, count: i64) -> Result[void, W::Error]:
out.begin_list(2)?
out.text(name)?
out.int(count)?
out.end_list()| Rule | Value | Serializer calls |
|---|---|---|
| std-serde.model.nullNull | an absent value | null() |
| std-serde.model.boolBoolean | false or true |
bool(value) |
| std-serde.model.intSigned | an integer that fits i64 |
int(value) |
| std-serde.model.uintUnsigned | an integer of an unsigned type | uint(value) |
| std-serde.model.floatFloat | any f64, NaN and infinities included |
float(value) |
| std-serde.model.textText | a string |
text(value) |
| std-serde.model.listList | items in order | begin_list(len), one value per item, end_list() |
| std-serde.model.mapMap | entries in order, each with a string key |
begin_map(len), then per entry key(k) and one value, then end_map() |
| std-serde.model.variantVariant | a struct value, or an enum value | begin_variant(facts, v), then per member member(m) and one value, then end_variant() |
- std-serde.model.one-valueA
serializecall writes exactly one value of the table: one scalar call, or one begin call, its contents, and the matching end call. - std-serde.model.lenThe
lenofbegin_listorbegin_mapis the number of items or entries that follow. - std-serde.model.variant-args.local-ref
begin_variant(facts: Facts, v: &VariantInfo)receives the type-level facts and a local reference to the variant's metadata. - std-serde.model.data-variantA struct value has one variant, whose
of_datais true. - std-serde.model.member-args.local-ref
member(m: &Member)receives a local reference to the metadata of the member whose value follows. - std-serde.model.member-factsThe member's facts are those visible to its derivation, as
annot.structure.per-derivationspecifies. - std-serde.model.errorsA writer's first
.Errends the write. Every standard implementation returns it unchanged, and calls nothing after it. - std-serde.model.ref-receiversEvery method of
SerializerandDeserializertakes&mut self, andserializeanddeserializetake the writer or reader as&mut Wor&mut R. So a format's writer or reader is a struct that changes in place, and its caller reads the struct's fields after the call.
Why. Fourteen calls cover JSON's kinds, and the variant calls keep names and facts, which a format needs to choose a key or a tag. Fewer calls than serde's 29 keep a format small, and a binary format can still tell
intfromuint.
#Reading
A Deserializer reads the same tree, one call per value:
| Rule | Call | Returns |
|---|---|---|
std-serde.read.peekpeek |
peek() |
the ValueKind of the next value, without reading it; an absent value is Null |
std-serde.read.is-nullis_null |
is_null() |
true when the next value is null or absent, which reads it; false otherwise, which leaves it |
| std-serde.read.scalarScalars | bool, int, uint, float, text |
the next value, when it is of that kind and fits the result type |
| std-serde.read.listList | begin_list(), then next_item() before each item |
next_item is true when an item follows, and false after the last |
| std-serde.read.mapMap | begin_map(), then next_key() before each entry |
next_key is the entry's key, or .None after the last |
| std-serde.read.variantVariant | begin_variant(facts, choices), then per member member(m) and one value, then end_variant() |
begin_variant is the index in choices of the variant read |
std-serde.read.invalidinvalid |
invalid(expected) |
the format's error for the value just read, which does not fit expected |
std-serde.read.absentis_absent |
is_absent() |
true when the member that member(m) just positioned has no value at all, which reads nothing; the trait's default body gives false |
- std-serde.read.one-valueA
deserializecall reads exactly one value. - std-serde.read.expectedEach scalar call's
expectednames the type being read, as"u8". A format's error for a value of another kind may report it. - std-serde.read.member.local-refA
member(m: &Member)call positions the next value of that member, which the format may report as absent. - std-serde.read.choices
choicesholds theVariantInfoof every variant of the type, in declaration order. A struct offers its one variant.
use std.serde.Deserializer
fn read_text_or_empty[R < Deserializer](input: &mut R) -> Result[string, R::Error]:
match input.peek()?:
.Text => input.text("string")
_ => .Ok("")Note.
peeklets a value that holds any kind, such as aJson, read itself. A format that is not self-describing may return an error frompeek.
#Derived Implementations
The templates drive a format's
writer or reader with a value's members. For the Session of
Serialization:
| Value | Serializer calls of derived serialize |
|---|---|
Session { user: "ada", token: "t", expires_at: 9 } |
begin_variant, member for user, text("ada"), member for token, text("t"), member for expires_at, int(9), end_variant() |
- std-serde.derive.serialize.template
std.serdedeclares the template ofSerialize, which@derive(Serialize)instantiates. - std-serde.derive.serialize.included-orderDerived
serializecallsbegin_variant, thenmember(m)andserializefor each included walked member in declaration order, thenend_variant(). - std-serde.derive.deserialize.template
std.serdedeclares the template ofDeserialize, which@derive(Deserialize)instantiates. - std-serde.derive.deserialize.unskipped-orderDerived
deserializeoffers every variant tobegin_variant, then callsmember(m)anddeserializefor each unskipped built member in declaration order, thenend_variant(). - std-serde.derive.deserialize.unskipped-defaultAfter
member(m), derived deserialization asksis_absent()for an unskipped defaulted member, using its declared default instead ofdeserializewhen absent. - std-serde.derive.omittedA member omitted with
= passis not written. Reading fills it with its declared default, asannot.omit.defaultstates. - std-serde.derive.sharedShared constructor data is not a member, so it is neither written nor read.
use std.serde.{Serialize, Deserialize}
use std.structure.Structure
struct Cache:
hits: i64
struct Session:
pub user: string
token: string
cache: Cache = Cache { hits: 0 }
impl Serialize for Session by Structure:
cache = pass
impl Deserialize for Session by Structure:
cache = passNote. The two blocks above leave
cacheout of every format. Their member lines agree, so they get noderivation-line-driftwarning.
#Member Names
A member's wire name is its name in a document format.
For example, @rename("total") on total_cents: i64 uses the key total.
-
std-serde.rename.fact
std.serdeexports the field factRenamewith the public fieldname: string. -
std-serde.rename.constructor
rename(name: string) -> Renameconstructs a fact with that name. -
std-serde.member-name.precedenceA member's wire name uses its
Rename.namefirst, its container'sRenameAllstyle second, and its source name otherwise. -
std-serde.rename.metadata
Member.nameretains the source member name, and document formats readRenamefromMember.facts. -
std-serde.member-name.helper.local-ref
member_name(member: &Member, container: Facts) -> stringreturns that member's wire name. -
std-serde.member-name.formatsStandard serializers and deserializers use
member_namefor document or capture-group names. -
std-serde.rename-all.styles
RenameCaseis anEqandDebugenum with the eight style variants in the table. -
std-serde.rename-all.fact
RenameAllis a data-type and enum fact with the public fieldstyle: RenameCase. -
std-serde.rename-all.constructor
rename_all(style: RenameCase) -> RenameAllconstructs the fact. -
std-serde.rename-all.conversionStyle conversion follows the table, applying ASCII case changes to a member's written name.
-
std-serde.rename-all.containerA standard format's container facts are the current record's or enum's per-derivation facts supplied to
begin_variant. -
std-serde.rename-all.map-keysA
RenameAllstyle does not change string map keys.
RenameCase variant |
Style | Conversion | order_id becomes |
|---|---|---|---|
Lowercase |
lowercase |
Keep the name | order_id |
Uppercase |
UPPERCASE |
Uppercase ASCII letters | ORDER_ID |
PascalCase |
PascalCase |
Remove underscores and uppercase each word's first character in ASCII | OrderId |
CamelCase |
camelCase |
PascalCase, then lowercase its first character in ASCII | orderId |
SnakeCase |
snake_case |
Keep the name | order_id |
ScreamingSnakeCase |
SCREAMING_SNAKE_CASE |
Uppercase ASCII letters | ORDER_ID |
KebabCase |
kebab-case |
Replace underscores with hyphens | order-id |
ScreamingKebabCase |
SCREAMING-KEBAB-CASE |
Uppercase ASCII letters and replace underscores with hyphens | ORDER-ID |
For example, @rename_all(.CamelCase) on OrderPaid gives the member
order_id the wire name orderId; a field's explicit @rename wins.
Why. Document names belong to the serialization view; preserving the source name lets other adapters, such as environment readers, name fields independently.
#Absent Optional Members
The typed SkipNone fact omits an optional member only when it is absent.
For example, @skip_none() on note: string? omits the key for .None.
- std-serde.skip-none.fact
std.serdeexports the member-typed field factSkipNone[F]for the field typeF. - std-serde.skip-none.constructor
skip_none[T]() -> SkipNone[T?]constructs the fact for an optional field, checked byannot.typed-fact.check. - std-serde.skip-none.omitDerived serialization omits a member marked by
skip_none()when its value is.None, without calling its serializer or the format'smember. - std-serde.skip-none.presentA marked
.Some(value)is written normally, including.Some(.None)as a present member whose value is null. - std-serde.skip-none.read
SkipNonedoes not change deserialization, so absent keys follow the ordinary optional and declared-default rules. - std-serde.skip-none.ordinaryAn unmarked optional member still writes
.Noneas null.
Why. The fact's optional predicate runs before the format sees the member, so every serializer receives the same view without confusing a present value that serializes as null with an absent optional.
#Skipped Members
The Skip field fact omits a member from its serde view.
For example, @skip() on cached: usize = 0 leaves the cache out of documents.
- std-serde.skip.fact
std.serdeexports the field factSkipwith no fields. - std-serde.skip.constructor
skip() -> Skipconstructs the fact. - std-serde.skip.writeDerived serialization omits a member with
Skipwithout calling its serializer or the format'smembermethod for it. - std-serde.skip.readDerived deserialization fills a skipped member from its declared default without positioning or decoding its input, even when a key is present.
- std-serde.skip.impl-factThe walker of derived serialization and the source of derived deserialization carry
skips::[Skip](). Soannot.impl-fact.skipsomits a member whose known facts holdSkip. - std-serde.skip.no-boundA skipped field's type need not implement
SerializeorDeserialize. - std-serde.skip.no-defaultUnder a derived
Deserialize, a skipped field without a declared default is an error at the opt-in. Error:omitted-member-without-default. - std-serde.skip.unknown-factA
Skipthat only a named list or a spread attaches is read when the program runs. The field keeps both bounds, and reading it without a declared default returns a deserializer error naming the field.
Why. A skipped member is never written or read, so its type owes serde nothing. Saying so on the walker and source keeps the rule in library code.
#Standard Implementations
std.serde implements both traits for these types:
| Rule | Type | serialize writes |
deserialize reads, with expected |
|---|---|---|---|
| std-serde.std.boolBoolean | bool |
bool |
bool, "bool" |
| std-serde.std.textString | string |
text |
text, "string" |
| std-serde.std.charCharacter | char |
text of that one character |
text of exactly one character, "char" |
| std-serde.std.signedSigned integers | i8, i16, i32, and i64 |
int |
int within the type's range, the type's name |
| std-serde.std.unsignedUnsigned integers | u8, u16, u32, u64, and usize |
uint |
uint within the type's range, the type's name |
| std-serde.std.floatFloats | f32 and f64 |
float of the value as an f64 |
float, the type's name |
| std-serde.std.optionalOptional | T?, where T implements the trait |
null for .None, and the payload for .Some |
.None when is_null is true, and .Some of what T reads otherwise |
| std-serde.std.listList | List[T], where T implements the trait |
a list of the items, in order | a list, item by item |
| std-serde.std.map-entriesMap | Map[string, V], where V implements the trait |
a map of the entries, in the map's iteration order | a map, entry by entry, in its order |
| std-serde.std.setSet | Set[T], where T implements the trait |
a list of the elements, in insertion order | a list, element by element; a repeated element keeps one entry |
| std-serde.std.tupleTuple | a tuple type whose members implement the trait | a list of the members, in order | a list with one item per member, in order |
| std-serde.std.referenceReference | &T and &mut T, where T implements the trait |
what T writes for the value the reference refers to |
a new object that holds what T reads |
- std-serde.std.rangeA value that its scalar call reads but the type cannot hold is the reader's
invalid(expected)error. That is an integer outside the type's range, achartext of another length, or anf32past its finite range. The range ofusizeis the target's, bytypes.usize.width, so on the Wasm32 target it is that ofu32. - std-serde.std.float-readAn
f32reads the nearestf32to thef64it reads. - std-serde.std.tuple-lengthA tuple reads exactly one list item per member. A list with fewer items, or with items left after the last member, is the reader's
invalid(expected)error, so JSON reports it at the path of the list or of the first extra item. - std-serde.std.tuple-restA rest element is one member of type
List[T], byannot.walk.rest.default. So(i32, List[i32]...)writes[1, [2, 3]], a list whose last item is the rest list. - std-serde.std.map-keysOnly a
Mapwhose key type isstringimplements the traits. Using a map with any other key type where they are required is an error. Error:unsatisfied-trait-bound.
use std.serde.Serialize
fn send[T < Serialize](value: T) -> void:
pass
fn send_counts(counts: Map[i32, i32]) -> void:
send(counts) # error: unsatisfied-trait-boundNote.
T??loses a layer:.Some(.None)writesnull, which reads back as.None.
Why. A map key is text in JSON, YAML, and TOML. A map with other keys converts them first, so the program chooses their text.
See also: Serialization, Typed JSON, Time, Boundary Encoding.