brb_encode
lib callCapy
function brb_encode(value : any) string
Description
brb_encode serializes an any value as BRRB version 2, also called BRRB2. Use brb_decode to restore the value.
BRRB is a tree format for maps, lists, strings, finite numbers, booleans, and none. It has no schema, compression, checksum, encryption, or object-reference table.
Document layout
All hexadecimal byte sequences below use spaces for readability. The spaces are not part of the document.
Offset Size in bytes Field Value
------ ------------- ------- -----------------------------------
0 4 Magic ASCII BRRB, hex 42 52 52 42
4 1 Version 02
5 Variable Root Exactly one node, as specified belowThe root can have any public node type. There is no document-length field, terminator, padding, or alignment. A decoder must consume the whole input after the root. Concatenated documents are not one valid document.
For a byte stream with several messages, add framing outside BRRB. For example, send a message length followed by that many document bytes. Both endpoints must agree on the outer framing.
Unsigned variable-length integers
Scalar lengths, child counts, and key lengths use unsigned base-128 variable-length integers, also called unsigned LEB128. They do not use a fixed byte order or a signed integer transform.
Each byte contributes its low seven bits. The first byte contains the least significant group. Bit 7 means that another byte follows.
To encode a value, emit its low seven bits. Set bit 7 if more bits remain, then shift the value right by seven. Repeat until no bits remain.
Decimal value Hex bytes
-------------------- -----------------------------
0 00
1 01
127 7f
128 80 01
255 ff 01
16384 80 80 01
18446744073709551615 ff ff ff ff ff ff ff ff ff 01The range is 0 through 2^64 - 1. Use the shortest encoding. A multi-byte encoding cannot end with a zero payload group. Thus 80 00 is not a valid encoding of zero.
A decoder rejects truncated values, more than ten bytes, and overflow. In a ten-byte value, the last byte must be 01.
Node layout
Every node has the following fields, in this order. Scalar nodes also carry the child-count field, which must be zero.
Field Size in bytes Meaning
-------------- ----------------------- --------------------------------------
Flags 1 00 normally, 01 for a list
Type 1 One ASCII type tag from the table below
Scalar length Variable-length integer Number of scalar payload bytes
Scalar payload Scalar length Raw bytes, without a terminator
Child count Variable-length integer Number of immediate child entries
Child entries Variable Exactly child-count entries, in sequenceThe child count does not include descendants of those children.
Each child entry contains a key followed by a complete node:
Field Size in bytes Meaning
---------- ----------------------- ----------------------------------
Key length Variable-length integer Number of key bytes
Key Key length Raw key bytes, without a terminator
Child node Variable The same node layout, recursivelyThere is no child-node length field. Parse a node's children to find the next sibling. All lengths count bytes, not Unicode characters.
Public type tags
Tag Hex Meaning Encoder scalar payload Children
--- --- ----------- ----------------------------- ------------
M 4d Map or list Empty Zero or more
S 53 String Exact string bytes Zero
I 49 s64 Signed decimal integer Zero
U 55 u64 Unsigned decimal integer Zero
F 46 f64 Text representation of an f64 Zero
B 42 Boolean ASCII 0 or 1 Zero
N 4e none Empty ZeroThe type tag distinguishes an empty string, an empty map, an empty list, and none. They are different values even when their payloads are empty.
Strings and keys
Strings and keys have no escape sequences or required character encoding. Text applications normally use UTF-8, but BRRB also preserves arbitrary bytes, including zero bytes. An empty key is valid.
Do not apply JSON escaping, URL encoding, or a trailing zero byte to a payload. For example, a three-byte string 41 00 42 has scalar length 03.
Numbers and Capy integer types
An F payload contains text, not binary IEEE 754 bits. The encoder uses general floating-point formatting with max_digits10 precision, which is 17 significant digits for f64. Exponent notation can occur. For example, 1.5 has payload 31 2e 35.
The decoder requires a finite result and must consume the whole payload. The current decoder uses strtod. It accepts more numeric spellings than the encoder emits, including leading whitespace and hexadecimal floating-point text. Trailing whitespace or zero bytes cause rejection.
Use ordinary finite decimal text for interoperability. Do not depend on a particular exponent spelling or treat the encoded bytes as a canonical numeric representation. The encoder can emit non-finite floating-point text, but brb_decode rejects it. NaN and infinity do not round-trip.
I preserves the full signed 64-bit range. U preserves the full unsigned 64-bit range. Each payload contains decimal digits, with an optional leading minus sign for I. The decoder requires at least one digit and complete input consumption. It rejects overflow, plus signs, whitespace, decimal points, exponents, and negative U values.
Capy boxes signed integer types as s64 and unsigned integer types as u64. Their tags are I and U. Floating-point types use F. Strings always use S, even when their bytes contain decimal digits. Copies, cloning, and public or local BRRB preserve these numeric kinds and values.
JSON encoding emits I and U as numeric tokens. Standard JSON decoding preserves integer-form tokens within signed and unsigned 64-bit ranges. Decimal, exponent, and out-of-range integer tokens use f64 and can round. Use BRRB when exact 64-bit integer values and kinds must survive a boundary.
Both endpoints need support for I and U. Earlier decoders reject these tags. Rebuild the runtime and core together before exchanging the corrected numeric values.
Booleans and none
The encoder emits only 0 and 1 for booleans. The decoder also accepts false, true, (false), and (true). These are case-sensitive ASCII strings.
A public N node must have flags 00, scalar length zero, and child count zero. It represents none, including a missing value that the caller passes to the encoder.
Maps and lists
Maps and lists both use M. Bit 0 of the flags byte distinguishes a list. The encoder writes 00 for a map and 01 for a list. Bits 1 through 7 are reserved. Write them as zero.
A map has an empty scalar payload. Its children have explicit string keys. The encoder writes map entries in insertion order.
A list also has an empty scalar payload. It still stores a key for every child. The encoder writes keys 0, 1, through child_count - 1 as ASCII decimal strings, in numeric order. Keys 10 and above follow 9, not 1.
For interoperable lists, use consecutive keys without signs or leading zeros. The decoder inserts children by key. A missing or out-of-order new index can turn the result into a map. The list flag does not repair malformed indexes.
The decoder accepts map entries in any order. If a key repeats, the last child replaces the earlier child. The parser still counts every child node toward its node limit.
Decoder acceptance and limits
The current decoder has these limits:
At most 262144 nodes in the document, including the root and all scalar nodes.
At most 64 node levels. The root has depth zero, so the deepest allowed node has depth 63.
Every length and count must use a valid, shortest unsigned variable-length integer.
Each payload must fit within the remaining input.
The root must end exactly at the end of the document.
There is no separate BRRB document-byte limit. Available memory and the caller's transport or storage limits still apply. Set a byte limit before accepting untrusted documents.
The decoder rejects unknown type tags, unsupported versions, malformed numbers or booleans, scalar children, and a list flag on a scalar node. It rejects every nonzero flag on N.
The decoder also accepts forms that the encoder never emits. The current decoder ignores reserved flag bits on M, S, I, U, F, and B. It also ignores a nonempty scalar payload on M. Do not emit these forms. Acceptance does not make them supported extensions.
The Capy brb_decode function returns a result. Invalid input has ok: false and error. Valid data has ok: true and the decoded value, including none.
The encoder does not enforce the decoder's node or nesting limits. Keep encoded trees within these limits when the receiver uses brb_decode. Do not encode cyclic values. The encoder recursively follows every child and has no cycle check.
Value sharing and unsupported values
BRRB stores values, not shared object identities. If two map entries refer to one any object, the encoder writes the object twice. After decoding, those entries contain separate objects. Changes to one do not change the other.
Public brb_encode converts a callable to N. It does not serialize executable code. Native raw-pointer values become empty S nodes. These values cannot round-trip through the public format.
The runtime also has a private local variant with tag C, hex 43, for callable handles. Its flags and child count are zero. Its eight-byte scalar contains two little-endian unsigned 32-bit fields, a local closure pointer followed by a callable type identifier.
Public brb_decode rejects C. These fields have meaning only within the originating runtime workspace. Do not use private local documents for files, network messages, or another process.
Complete byte examples
Each row is a complete document, including the five-byte header.
Value Hex bytes
--------------------- -----------------------------------
none 42 52 52 42 02 00 4e 00 00
Empty string 42 52 52 42 02 00 53 00 00
Empty map 42 52 52 42 02 00 4d 00 00
Empty list 42 52 52 42 02 01 4d 00 00
false 42 52 52 42 02 00 42 01 30 00
true 42 52 52 42 02 00 42 01 31 00
Number 1.5 42 52 52 42 02 00 46 03 31 2e 35 00
String Ada 42 52 52 42 02 00 53 03 41 64 61 00
String bytes 41 00 42 42 52 52 42 02 00 53 03 41 00 42 00For {"name": "Ada"}, the document is 21 bytes:
42 52 52 42 02 magic and version
00 4d 00 01 map, empty scalar, one child
04 6e 61 6d 65 key length 4, key "name"
00 53 03 41 64 61 00 string "Ada", no childrenFor a list with true and none, the document is 22 bytes:
42 52 52 42 02 magic and version
01 4d 00 02 list, empty scalar, two children
01 30 key length 1, key "0"
00 42 01 31 00 boolean true, no children
01 31 key length 1, key "1"
00 4e 00 00 none, no childrenFor a string of 128 ASCII x bytes, the scalar length needs two bytes:
42 52 52 42 02 magic and version
00 53 80 01 string, scalar length 128
78 ... 78 exactly 128 payload bytes
00 no childrenThis document is 138 bytes. The ellipsis is explanatory text, not wire data.
Parameters
value : value to encode
Return Values
A binary string with one BRRB version 2 document. The string can contain zero bytes. Use base64_encode for text-only storage or transport.
Example
Capy
var packed := brb_encode({"name": "Ada"})
print(brb_decode(packed).value["name"], "\n")
Output
Ada