runtime:serialization

XML, YAML, TOML, CSV, MessagePack, CBOR and JSONL parse in Rust — the text or bytes cross the op boundary once and a JavaScript value comes back. BSON is encoded and decoded by the engine itself, straight from and into JavaScript objects, and JSONC and JSON5 are parsed the same way. Protobuf is the exception: it is a reflective pure-JavaScript implementation, because a .proto is compiled at run time rather than by a build step.

Capability: none

Exposed as an ES module under the runtime: scheme. Status: Available.

Import

JavaScript
import {
  JSONL, JSONC, JSON5, XML, YAML, TOML, CSV, MessagePack, CBOR, BSON, Protobuf
} from "runtime:serialization";

Every build/encode throws TypeError on a circular value and RangeError past 256 levels of nesting.

JSONL (JSON Lines)

Stream-based parsing and serializing of JSON Lines data. JSONL is heavily optimized for large file handling natively over streams.

ExportTypeDescriptionExample
new JSONL.DecoderStream(options?)TransformStreamParses streaming JSONL byte chunks into JS objects incrementally. Option skipInvalid? (boolean, default false): skip invalid JSON lines instead of destroying the stream; skipped lines are emitted to decoder.onError(err).stream.pipeThrough(new JSONL.DecoderStream({ skipInvalid: true }))
new JSONL.EncoderStream()TransformStreamSerializes a stream of JS objects into JSONL byte chunks. No options are currently supported.stream.pipeThrough(new JSONL.EncoderStream())

JSONC and JSON5

JSON with comments and JSON5 (D191). Both mirror the global JSON.

ExportDescription
JSONC.parse(text, reviver?)Parses JSON with // and /* */ comments and one trailing comma in an object or array; a leading byte order mark is ignored. Returns what JSON.parse builds from the same JSON, and runs reviver as it does.
JSONC.stringify(value, replacer?, space?)Standard JSON, exactly as JSON.stringify writes it: a comment cannot come from a value.
JSONC.validate(text, opts?)Checks the text without building a value; agrees with parse. opts.detailed returns { valid, error? }.
JSON5.parse(text, reviver?)Parses JSON5 1.0.0: identifier keys, single-quoted and multi-line strings, hex, Infinity, NaN, leading +, leading and trailing decimal points, comments and trailing commas. For the JSON it shares, the value is what JSON.parse builds.
JSON5.stringify(value, replacer?, space?)Writes JSON5: ASCII identifier keys unquoted, Infinity and NaN kept, each string in whichever quote needs fewer escapes (single on a tie), and a trailing comma after each member when indenting. A value's toJSON5() is used before its toJSON(). Also takes (value, { replacer, space, quote }), where quote is "'" or '"'.
JSON5.validate(text, opts?)As JSONC.validate.
JSONC.edit(text, path, value), JSON5.edit(text, path, value)The text with the value at path (an array of keys and indices) set to value, or removed when value is undefined. Every other byte is kept: comments, blank lines, key order and quotes. An existing value is replaced where it stands (of a repeated key, the last); a missing key is added after its siblings with their indent, colon spacing, trailing comma and, in JSON5, quoting; missing objects on the path are created; an index equal to an array's length appends. A removal takes the member's comma and a comment on the rest of its line, and every occurrence of a repeated key. Text that does not parse is a SyntaxError; a path through a value that cannot hold it, or a value with no form in the format, is a TypeError; an index past an array's end is a RangeError.

Values follow JSON.parse: a __proto__ key is an own property and never sets the prototype, a repeated key keeps its first place and its last value, and a \u escape may spell a lone surrogate. Text that is not in the dialect is a SyntaxError whose message names it and ends at line L column C; nesting past 256 levels is a RangeError. JSON5.stringify skips and refuses what JSON.stringify does: undefined, functions and symbols are skipped (null in an array), and a BigInt or a circular value is a TypeError.

XML

Synchronous parsing, building, and validation of XML data, plus streaming support for massive documents.

ExportTypeDescriptionExample
XML.parse(xml)(string) => objectParses an XML string into a JavaScript object.XML.parse("<root>hi</root>")
XML.build(obj)(object) => stringSerializes a JavaScript object into an XML string.XML.build({ root: "hi" })
XML.validate(xml, options?)(string, object) => boolean | objectValidates an XML string. Option detailed? (boolean, default false): return { valid, error? } instead of a boolean, with the exact syntax error on failure.XML.validate("<root>", { detailed: true })
new XML.DecoderStream()TransformStreamParses streaming XML byte chunks into JavaScript objects incrementally.stream.pipeThrough(new XML.DecoderStream())

parse returns an object with the root element as its one key: attributes are @name, an element's text is $text, a repeated name is an array, and an empty element (<a/> or <a></a>) is "". References and CDATA are text.

InputResult
More than one root, or text outside itSyntaxError
An unclosed element, or no element at allSyntaxError
A reference other than the five predefined entities and &#…; (a DTD is not processed)SyntaxError
A repeated attribute, < in an attribute value, a name that is not an XML nameSyntaxError
Nesting past 256 levelsSyntaxError

build writes @name keys as attributes, $text as text, an array as a repeated element and null/undefined as an empty element. Numbers are written as String(n), a BigInt exactly. An invalid name, NaN/±Infinity, bytes, a character XML 1.0 cannot contain, or an array at the top level is a TypeError. DecoderStream emits one { name: value } per direct child of the root, read by the same reader: a malformed record or a truncated document errors the stream.

YAML

Synchronous parsing, building, and validation of YAML 1.2.

ExportTypeDescriptionExample
YAML.parse(yaml)(string) => objectParses a YAML string into a JavaScript object.YAML.parse("key: value")
YAML.build(obj)(object) => stringSerializes a JavaScript object into a YAML string.YAML.build({ key: "value" })
YAML.validate(yaml, options?)(string, object) => boolean | objectValidates a YAML string. Option detailed? (boolean, default false): return { valid, error? } instead of a boolean.YAML.validate("key: value", { detailed: true })

parse reads one document; an empty stream is null. Plain scalars resolve by the YAML 1.2 core schema: 017 is 17, yes is "yes", .inf and .nan are Infinity and NaN. Keys keep their source text (1: a gives the key "1"). Merge keys (<<) are applied, and a key written in the mapping wins over a merged one.

InputResult
More than one documentSyntaxError
A duplicate key in one mappingSyntaxError
A sequence or mapping as a keySyntaxError
A tag other than !!str, !!int, !!float, !!bool, !!null, !!seq, !!map, !!binary, !!timestamp, !!set, !!omap, !!pairsSyntaxError
Nesting deeper than 256 levelsRangeError
Aliases expanding to more than 1,000,000 nodesRangeError

build writes block style with two-space indentation. A string is quoted when, written plain, it would read back as something else — under the core schema, or under the wider schema js-yaml applies by default and YAML 1.1 readers (PyYAML, SnakeYAML) use: the 1.1 booleans (yes, off, y), timestamps (2001-12-14), base-60 numbers (1:20), 0b integers and _-separated numbers. js-yaml's dumper quotes the same set. A number is written as String(n), a BigInt exactly, NaN and ±Infinity as .nan and ±.inf, a Date as its ISO-8601 string, and undefined as null. Bytes, a function or a symbol is a TypeError.

TOML

Synchronous parsing, building, and validation of TOML data.

ExportTypeDescriptionExample
TOML.parse(toml)(string) => objectParses a TOML string into a JavaScript object.TOML.parse("key = 'value'")
TOML.build(obj)(object) => stringSerializes a JavaScript object into a TOML string. The root must be an object/table.TOML.build({ key: "value" })
TOML.validate(toml, options?)(string, object) => boolean | objectValidates a TOML string. Option detailed? (boolean, default false): return { valid, error? } instead of a boolean.TOML.validate("key = 'value'", { detailed: true })

Keys keep their document order. An integer past 253 parses to a BigInt (TOML integers are lossless); datetimes parse to RFC 3339 strings.

build inputWritten as
integral number in the safe rangeinteger
any other number, NaN, ±Infinityfloat (nan, inf, -inf)
BigInt within 64 bitsinteger
DateISO-8601 string
null, undefined, bytes, a BigInt past 64 bitsthrows TypeError

CSV

Synchronous parsing, building, and validation of CSV. CSV.parse returns arrays of fields (string[][]) by default and only keys objects when asked; every field is a string unless cast is set.

ExportTypeDescriptionExample
CSV.parse(csv, options?)(string, object) => anyParses CSV text. string[][] by default, or an array of objects when header is set.CSV.parse("a,b\n1,2\n")
CSV.build(rows, options?)(any, object) => stringWrites an array of rows (arrays, or objects keyed by header) as CSV text.CSV.build([["a", "b"]])
CSV.validate(csv, options?)(string, object) => boolean | objectValidates under the same options as parse. Option detailed? (default false): return { valid, error? }.CSV.validate("a,b\n1,2\n")
CSV.DecoderStream(options?)TransformStream<string | Uint8Array, any>Streams rows from text/byte chunks, with parse's options and row shapes. A quoted field may span chunks.source.pipeThrough(new CSV.DecoderStream({ header: true }))
CSV.EncoderStream(options?)TransformStream<any, string>Streams CSV text from rows (one per chunk), with build's options and shape rules.rows.pipeThrough(new CSV.EncoderStream())
OptionDefaultMeaning
delimiter","Field separator (a single byte).
quote"\""Quote character (a single byte).
escapedoublingEscape inside a quoted field; the default is RFC 4180 "".
headerfalsefalse → arrays; true → keys objects by the first row (consumed); a string[] → names every row and treats the first row as data.
trimfalseTrim whitespace from unquoted fields, and skip spaces and tabs around a quoted one (a, "b, c"). A tab delimiter is never trimmed.
skipEmptyLinesfalseDrop records that are a single empty field.
comment—A single byte, other than the delimiter, the quote or a line break; lines beginning with it are skipped whole. build quotes a field that starts with it so the row survives a round trip.
castfalseConvert plain decimal fields to numbers and true/false to booleans. inf, nan, hex, an overflowing decimal, and an integer past 253 − 1 stay strings.
relaxColumnCountfalseAllow rows whose field count differs from the header.
maxFieldBytes16 MiBPer-field byte cap.
maxColumns1,048,576Per-record field cap.
bomtrueStrip a leading UTF-8 BOM.
newline"\n"The record separator build writes.

A quoted field keeps delimiters and newlines, and "" reads back as one ". With header set, every row must match the header's field count unless relaxColumnCount is on. A duplicate column name (inferred or explicit), a stray character after a closing quote, and a delimiter or quote that is a line break (or a delimiter equal to the quote, or a comment equal to either) are errors. build writes an absent (null/undefined) field empty, a number as String(n) spells it and a BigInt exactly, and quotes a field that starts with the comment byte. Unlike XML/YAML/TOML, CSV.validate forwards the options, because a ragged row is valid without a header and invalid with one.

MessagePack

Synchronous parsing, building, and validation of binary MessagePack data.

ExportTypeDescriptionExample
MessagePack.decode(msgpack)(Uint8Array) => objectParses a MessagePack byte array into a JavaScript object.MessagePack.decode(bytes)
MessagePack.encode(obj)(object) => Uint8ArraySerializes a JavaScript object into a MessagePack byte array.MessagePack.encode({ key: "value" })
MessagePack.validate(msgpack, options?)(Uint8Array, object) => boolean | objectValidates a MessagePack byte array. Option detailed? (boolean, default false): return { valid, error? } instead of a boolean.MessagePack.validate(bytes, { detailed: true })

Type mapping

JavaScriptWireDecodes back as
Uint8Array, any view, ArrayBufferbinUint8Array
Mapmap (keys stringified)object
SetarrayArray
DateISO-8601 strstring
function, symbol, BigInt—throws TypeError
—extUint8Array (payload)

A non-string map key becomes a property name by the CBOR rule below. A reserved marker (0xc1), a truncated value, a str that is not UTF-8, nesting past 256 levels, or a byte after the top-level value is a SyntaxError; validate agrees with decode.

CBOR

Synchronous decoding, encoding, and validation of CBOR (RFC 8949, Concise Binary Object Representation). encode writes the deterministic form: shortest integer and length forms, the shortest float that preserves the value, and bytewise-sorted map keys.

ExportTypeDescriptionExample
CBOR.decode(cbor)(Uint8Array) => anyDecodes a CBOR byte array into a JavaScript value. Accepts definite and indefinite-length strings, arrays and maps; refuses tags.CBOR.decode(bytes)
CBOR.encode(obj)(any) => Uint8ArrayEncodes a JavaScript value into a CBOR byte array in the deterministic form of RFC 8949 §4.2.CBOR.encode({ key: "value" })
CBOR.validate(cbor, options?)(Uint8Array, object) => boolean | objectValidates a CBOR byte array, agreeing with decode. Option detailed? (boolean, default false): return { valid, error? } instead of a boolean.CBOR.validate(bytes, { detailed: true })

Type mapping

JavaScriptWireDecodes back as
Uint8Array, any view, ArrayBufferbyte string (major 2)Uint8Array
Mapmap (keys stringified)object
SetarrayArray
DateISO-8601 text stringstring
undefined / null0xf7 / 0xf6undefined / null
BigInt in [−264, 264−1]integer (major 0/1)number in the safe range, else BigInt
String/Number/Boolean/BigInt objectits primitivethe primitive
function, symbol, BigInt outside that range—throws TypeError
—tag (major 6)throws SyntaxError

An integer-valued number inside the safe range (±(253−1)) encodes as an integer, so a decoded 100000.0 re-encodes as 100000; past it a number is the shortest exact float and decodes back as a number, while a BigInt takes the integer form. A reserved initial byte, a stray break, a trailing byte, a malformed or oversized length, an unassigned simple value (or one below 32 in the two-byte form), a tag, or nesting past 256 levels is a SyntaxError.

BSON

MongoDB's document format. The BSON types are classes on the namespace.

JavaScript
const bytes = BSON.encode({ _id: new BSON.ObjectId(), count: 5n, at: new Date() });
const doc = BSON.decode(bytes); // { _id: ObjectId, count: 5n, at: Date }
ExportTypeDescription
BSON.encode(document)(object) => Uint8ArrayEncodes an object, or a Map with string keys.
BSON.decode(bytes)(Uint8Array | ArrayBuffer) => objectDecodes one BSON document.
BSON.validate(bytes, options?)(Uint8Array, object) => boolean | objectValidates the bytes, agreeing with decode. Option detailed?: return { valid, error? }.
new BSON.ObjectId(id?)ObjectIdA new id, or one from 24 hex digits, 12 bytes or another ObjectId. toHexString(), getTimestamp(), equals(), id; ObjectId.isValid(), createFromHexString(), createFromTime(seconds).
new BSON.Decimal128(text | bytes)Decimal128A 128-bit decimal, parsed exactly: text that would need rounding is a SyntaxError. toString(), bytes.
new BSON.UUID(value?)UUIDA new random UUID, or one from text (dashes optional) or 16 bytes. Binary subtype 4. toString(), toHexString(includeDashes?), equals(), bytes.
new BSON.Timestamp({ t, i } | bigint)TimestampA server timestamp; t and i are 0 to 232−1.
new BSON.Binary(buffer?, subType?)BinaryBinary data of another subtype. Binary.fromFloat32Array(), fromInt8Array(), fromPackedBits(bits, padding?) make vectors (subtype 9); toFloat32Array(), toInt8Array(), toPackedBits() read them.
new BSON.Code(code, scope?)CodeJavaScript code, with an optional scope document.
new BSON.BSONRegExp(pattern, options?)BSONRegExpA regular expression as the server stores it (options from ilmsux). toRegExp() is a SyntaxError for l or x.
new BSON.MinKey(), new BSON.MaxKey()Sort below and above every other value.
new BSON.Double(value)DoubleEncodes as a double even when whole.

Type mapping

JavaScriptBSONDecodes back as
whole number in the int32 range (not -0)int32number
any other numberdoublenumber
bigint in the int64 rangeint64bigint, however small
undefined in an object / an arrayomitted / null— / null
DatedatetimeDate
RegExpregular expression (flags i, m, s, u)BSONRegExp
Uint8Array, ArrayBufferbinary subtype 0Uint8Array
Map, any other objectdocument (own enumerable string keys)plain object
the BSON.* classestheir typesthe same classes
—deprecated undefined, symbol, DBPointernull, string, { $ref, $id }

A decoded double that is a whole number re-encodes as int32, as in the official driver; BSON.Double keeps it a double. A repeated key decodes with its last value. A NUL in a key, a cycle, a function, a symbol, a Set or a typed array other than Uint8Array is a TypeError; a bigint outside int64, an Invalid Date or nesting past 256 levels is a RangeError; malformed bytes are a SyntaxError.

Protobuf

Schema-aware Protobuf decoding and encoding. Pure-JS and reflective: the .proto is compiled at runtime (proto3 and editions 2023/2024; proto2-only constructs are rejected). Decoded objects use camelCase keys, BigInt for 64-bit ints, enum value-names, and Uint8Array for bytes.

ExportTypeDescriptionExample
new Protobuf.Schema(proto, options?)SchemaCompiles a .proto source string (or a { filename: source } map for multi-file schemas with imports; google/protobuf well-known types resolve automatically).new Protobuf.Schema('syntax = "proto3"; message Hello { string name = 1; }')
Protobuf.Schema.fromDescriptorSet(bytes)(Uint8Array) => SchemaBuilds a Schema from a compiled FileDescriptorSet (protoc --descriptor_set_out, ideally with --include_imports) instead of .proto source.Protobuf.Schema.fromDescriptorSet(await readDescriptorBytes())
schema.decode(messageName, bytes)(string, Uint8Array) => objectDecodes a byte array into a JavaScript object for the fully-qualified message name.schema.decode("Hello", bytes)
schema.encode(messageName, value, options?)(string, object, object) => Uint8ArrayEncodes a JavaScript object into a Protobuf byte array. Field names may be snake_case or camelCase; a key matching no field throws unless ignoreUnknownFields is set.schema.encode("Hello", { name: "world" })
schema.encodeDelimited(messageName, value, options?)(string, object, object) => Uint8ArrayEncodes one length-delimited message (varint length prefix + bytes). Concatenate results to write a stream. Same field-name rules as encode.schema.encodeDelimited("Hello", { name: "world" })
schema.decodeDelimited(messageName, source)(string, ReadableStream | AsyncIterable | Iterable | Uint8Array) => AsyncGenerator<object>Streams the messages of a length-delimited stream from a chunked byte source.for await (const m of schema.decodeDelimited("Hello", res.body)) { … }
schema.toJson(messageName, value)(string, object) => JsonValueConverts a decoded value to canonical proto3-JSON (64-bit ints and bytes as strings, enums as value-names, well-known-type special forms).schema.toJson("Hello", value)
schema.fromJson(messageName, json, options?)(string, JsonValue, { ignoreUnknownFields? }) => objectParses canonical proto3-JSON into the decoded value shape (ready for encode). Strict by default.schema.fromJson("Hello", { name: "world" })
schema.decodeStream(messageName, fieldName, source)(string, string, ReadableStream | AsyncIterable | Iterable) => AsyncGenerator<object>Streams the elements of a repeated message field from a chunked byte source, yielding each as it arrives without materializing the whole array.for await (const item of schema.decodeStream("Catalog", "books", stream)) { … }

Errors

ErrorWhen
SyntaxErrorParsing fails due to malformed XML, YAML, TOML, CSV, JSONC, JSON5, CBOR or BSON input (an unterminated quote, a duplicate column name, a ragged row under header, a CBOR tag or trailing byte).
TypeErrorBuilding fails because the provided JavaScript object cannot be serialized into the target format (e.g., circular references, invalid keys).
RangeErrorThe input exceeds parser depth or memory limits (e.g., XML streaming buffer cap, YAML alias expansion).
Last updated on
Edit this page