CBOR is "Concise Binary Object Representation", defined by RFC 8949. Like JSON, but binary, more compact, and supporting a much broader range of data types.
cborg focuses on strictness and deterministic data representations. CBORs flexibility leads to problems where determinism matters, such as in content-addressed data where your data encoding should converge on same-bytes for same-data. cborg helps aleviate these challenges.
cborg is also fast, and is suitable for the browser (is Uint8Array native) and Node.js.
cborg bin2diag [binary input]cborg bin2hex [binary string]cborg bin2json [--pretty] [binary input]cborg diag2bin [diagnostic string]cborg diag2hex [diagnostic string]cborg diag2json [--pretty] [diagnostic string]cborg hex2bin [hex string]cborg hex2diag [hex string]cborg hex2json [--pretty] [hex string]cborg json2bin [json string]cborg json2diag [json string]cborg json2hex '[json string]'cborg/extended)
cborg/taglib)
import { encode, decode } from 'cborg'
const decoded = decode(Buffer.from('a16474686973a26269736543424f522163796179f5', 'hex'))
console.log('decoded:', decoded)
console.log('encoded:', encode(decoded))
decoded: { this: { is: 'CBOR!', yay: true } }
encoded: Uint8Array(21) [
161, 100, 116, 104, 105, 115,
162, 98, 105, 115, 101, 67,
66, 79, 82, 33, 99, 121,
97, 121, 245
]
When installed globally via npm (with npm install cborg --global), the cborg command will be available that provides some handy CBOR CLI utilities. Run with cborg help for additional details.
The following commands take either input from the command line, or if no input is supplied will read from stdin. Output is printed to stdout. So you can cat foo | cborg <command>.
cborg bin2diag [binary input]Convert CBOR from binary input to a CBOR diagnostic output format which explains the byte contents.
$ cborg hex2bin 84616161620164f09f9880 | cborg bin2diag
84 # array(4)
61 # string(1)
61 # "a"
61 # string(1)
62 # "b"
01 # uint(1)
64 f09f # string(2)
f09f9880 # "π"
cborg bin2hex [binary string]A utility method to convert a binary input (stdin only) to hexadecimal output (does not involve CBOR).
cborg bin2json [--pretty] [binary input]Convert CBOR from binary input to JSON format.
$ cborg hex2bin 84616161620164f09f9880 | cborg bin2json
["a","b",1,"π"]
cborg diag2bin [diagnostic string]Convert a CBOR diagnostic string to a binary data form of the CBOR.
$ cborg json2diag '["a","b",1,"π"]' | cborg diag2bin | cborg bin2hex
84616161620164f09f9880
cborg diag2hex [diagnostic string]Convert a CBOR diagnostic string to the CBOR bytes in hexadecimal format.
$ cborg json2diag '["a","b",1,"π"]' | cborg diag2hex
84616161620164f09f9880
cborg diag2json [--pretty] [diagnostic string]Convert a CBOR diagnostic string to JSON format.
$ cborg json2diag '["a","b",1,"π"]' | cborg diag2json
["a","b",1,"π"]
cborg hex2bin [hex string]A utility method to convert a hex string to binary output (does not involve CBOR).
cborg hex2diag [hex string]Convert CBOR from a hexadecimal string to a CBOR diagnostic output format which explains the byte contents.
$ cborg hex2diag 84616161620164f09f9880
84 # array(4)
61 # string(1)
61 # "a"
61 # string(1)
62 # "b"
01 # uint(1)
64 f09f # string(2)
f09f9880 # "π"
cborg hex2json [--pretty] [hex string]Convert CBOR from a hexadecimal string to JSON format.
$ cborg hex2json 84616161620164f09f9880
["a","b",1,"π"]
$ cborg hex2json --pretty 84616161620164f09f9880
[
"a",
"b",
1,
"π"
]
cborg json2bin [json string]Convert a JSON object into a binary data form of the CBOR.
$ cborg json2bin '["a","b",1,"π"]' | cborg bin2hex
84616161620164f09f9880
cborg json2diag [json string]Convert a JSON object into a CBOR diagnostic output format which explains the contents of the CBOR form of the input object.
$ cborg json2diag '["a", "b", 1, "π"]'
84 # array(4)
61 # string(1)
61 # "a"
61 # string(1)
62 # "b"
01 # uint(1)
64 f09f # string(2)
f09f9880 # "π"
cborg json2hex '[json string]'Convert a JSON object into CBOR bytes in hexadecimal format.
$ cborg json2hex '["a", "b", 1, "π"]'
84616161620164f09f9880
encode(object[, options])import { encode } from 'cborg'
Encode a JavaScript object and return a Uint8Array with the CBOR byte representation.
Date or a RegExp or another exotic type, you should either form them into intermediate forms before encoding or enable a tag encoder (see Type encoders).
null, undefined, number, bigint, string, boolean, Array, Object, Map, Buffer, ArrayBuffer, DataView, Uint8Array and all other TypedArrays (the underlying byte array of TypedArrays is encoded, so they will all round-trip as a Uint8Array since the type information is lost).Numbers will be encoded as integers if they don't have a fractional part (1 and 1.0 are both considered integers, they are identical in JavaScript). Otherwise they will be encoded as floats.Number.MAX_SAFE_INTEGER or less than Number.MIN_SAFE_INTEGER will be encoded as floats. There is no way to safely determine whether a number has a fractional part outside of this range.BigInts are supported by default within the 64-bit unsigned range but will be also be encoded to their smallest possible representation (so will not round-trip as a BigInt if they are smaller than Number.MAX_SAFE_INTEGER). Larger BigInts require a tag (officially tags 2 and 3).float64 option is supplied.rfc8949EncodeOptions to apply this rule.true, false, undefined and null. "Simple values" outside of this range are intentionally not supported (pull requests welcome to enable them with an option).float64 (boolean, default false): do not attempt to store floats as their smallest possible form, store all floats as 64-bittypeEncoders (object): a mapping of type name to function that can encode that type into cborg tokens. This may also be used to reject or transform types as objects are dissected for encoding. See the Type encoders section below for more information.mapSorter (function): a function taking two arguments, where each argument is a Token, or an array of Tokens representing the keys of a map being encoded. Similar to other JavaScript compare functions, a -1, 1 or 0 (which shouldn't be possible) should be returned depending on the sorting order of the keys. See the source code for the default sorting order which uses the length-first rule recommendation from RFC 7049.ignoreUndefinedProperties (boolean, default false): when encoding a plain object, properties with undefined values will be omitted. Does not apply to Maps or arrays.encodeInto(data, destination[, options])import { encodeInto } from 'cborg'
Encode a JavaScript object directly into a provided Uint8Array destination buffer, returning an object with a written property indicating the number of bytes written.
This API mirrors TextEncoder.encodeInto() and is useful for performance-critical scenarios where you want to avoid allocations by reusing a buffer.
const destination = new Uint8Array(1024)
const { written } = encodeInto({ hello: 'world' }, destination)
const encoded = destination.subarray(0, written)
If the destination buffer is too small to hold the encoded data, an error will be thrown. Use encodedLength() to pre-calculate the required size if needed.
The same encoding rules and options as encode() apply.
decode(data[, options])import { decode } from 'cborg'
Decode valid CBOR bytes from a Uint8Array (or Buffer) and return a JavaScript object.
BigInt.true, false, undefined and null. "Simple values" outside of this range are intentionally not supported (pull requests welcome to enable them with an option).allowIndefinite (boolean, default true): when the indefinite length additional information (31) is encountered for any type (arrays, maps, strings, bytes) or a "break" is encountered, an error will be thrown.allowUndefined (boolean, default true): when major 7, minor 23 (undefined) is encountered, an error will be thrown. To disallow undefined on encode, a custom type encoder for 'undefined' will need to be supplied.coerceUndefinedToNull (boolean, default false): when both allowUndefined and coerceUndefinedToNull are set to true, all undefined tokens (major 7 minor 23: 0xf7) will be coerced to null tokens, such that undefined is an allowed token but will not appear in decoded values.allowInfinity (boolean, default true): when an IEEE 754 Infinity or -Infinity value is encountered when decoding a major 7, an error will be thrown. To disallow Infinity and -Infinity on encode, a custom type encoder for 'number' will need to be supplied.allowNaN (boolean, default true): when an IEEE 754 NaN value is encountered when decoding a major 7, an error will be thrown. To disallow NaN on encode, a custom type encoder for 'number' will need to be supplied.allowBigInt (boolean, default true): when an integer outside of the safe integer range is encountered, an error will be thrown. To disallow BigInts on encode, a custom type encoder for 'bigint' will need to be supplied.strict (boolean, default false): when decoding integers, including for lengths (arrays, maps, strings, bytes), values will be checked to see whether they were encoded in their smallest possible form. If not, an error will be thrown.
useMaps (boolean, default false): when decoding major 5 (map) entries, use a Map rather than a plain Object. This will nest for any encountered map. During encode, a Map will be interpreted as an Object and will round-trip as such unless useMaps is supplied, in which case, all Maps and Objects will round-trip as Maps. There is no way to retain the distinction during round-trip without using a custom tag.rejectDuplicateMapKeys (boolean, default false): when the decoder encounters duplicate keys for the same map, an error will be thrown when this option is set. This is an additional strictness option, disallowing data-hiding and reducing the number of same-data different-bytes possibilities where it matters.retainStringBytes (boolean, default false): when decoding strings, retain the original bytes on the Token object as byteValue. Since it is possible to encode non-UTF-8 characters in strings in CBOR, and JavaScript doesn't properly handle non-UTF-8 in its conversion from bytes (TextEncoder or Buffer), this can result in a loss of data (and an inability to round-trip). Where this is important, a token stream should be consumed instead of a plain decode() and the byteValue property on string tokens can be inspected (see lib/diagnostic.js for an example of its use.)tags (array): a mapping of tag number to tag decoder function. By default no tags are supported. See Tag decoders.tokenizer (object): an object with two methods, next() which returns a Token, done() which returns a boolean and pos() which returns the current byte position being decoded. Can be used to implement custom input decoding. See the source code for examples. (Note en-US spelling "tokenizer" used throughout exported methods and types, which may be confused with "tokeniser" used in these docs).decodeFirst(data[, options])import { decodeFirst } from 'cborg'
Decode valid CBOR bytes from a Uint8Array (or Buffer) and return a JavaScript object and the remainder of the original byte array that was not consumed by the decode. This can be useful for decoding concatenated CBOR objects, which is often used in streaming modes of CBOR.
The returned remainder Uint8Array is a subarray of the original input Uint8Array and will share the same underlying buffer. This means that there are no new allocations performed by this function and it is as efficient to use as decode but without the additional byte-consumption check.
The options for decodeFirst are the same as for decode(), but the return type is different and decodeFirst() will not error if a decode operation doesn't consume all of the input bytes.
The return value is an array with two elements:
value: the decoded JavaScript objectremainder: a Uint8Array containing the bytes that were not consumed by the decode operationimport { decodeFirst } from 'cborg'
let buf = Buffer.from('a16474686973a26269736543424f522163796179f564746869736269736543424f522163796179f5', 'hex')
while (buf.length) {
const [value, remainder] = decodeFirst(buf)
console.log('decoded:', value)
buf = remainder
}
decoded: { this: { is: 'CBOR!', yay: true } }
decoded: this
decoded: is
decoded: CBOR!
decoded: yay
decoded: true
encodedLength(data[, options])import { encodedLength } from 'cborg/length'
Calculate the byte length of the given data when encoded as CBOR with the options provided. The options are the same as for an encode() call. This calculation will be accurate if the same options are used as when performing a normal encode(). Some encode options can change the encoding output length.
A tokensToLength() function is available which deals directly with a tokenized form of the object, but this only recommended for advanced users.
TaggedFor applications that need to wrap a value in an application-specific CBOR tag (COSE envelopes, dCBOR application tags, custom protocols) without the ceremony of registering a typeEncoders entry and a matching tag decoder, cborg exports a Tagged wrapper class. Tagged is symmetric: pass it to encode() to emit a tag, and use Tagged.decoder(tag) or Tagged.preserve(...tags) on the decode side to round-trip the tag without losing it.
import { encode, decode, Tagged } from 'cborg'
// Encode: wrap any value in a tag
const bytes = encode(new Tagged(1234, 'hello'))
// Decode: preserve the tag through decode
const decoded = decode(bytes, { tags: Tagged.preserve(1234) })
decoded instanceof Tagged // true
decoded.tag // 1234
decoded.value // 'hello'
Tagged.value can be anything cborg can encode, including arrays, maps, other Tagged instances, or values handled by your own typeEncoders β cborg recurses into it with the same encode pipeline.
For finer-grained control on decode, use Tagged.decoder(tag) directly to mix preserved tags with other decoders:
import { decode, Tagged } from 'cborg'
const value = decode(bytes, {
tags: {
16: Tagged.decoder(16), // preserve tag 16 as Tagged
96: Tagged.decoder(96), // preserve tag 96 as Tagged
42: cidDecoder // a custom decoder for tag 42
}
})
When to prefer Tagged over a custom typeEncoders entry:
{ tag, value }.When to prefer a typeEncoders entry instead:
CID β tag 42, Date β tag 1) and want it to apply automatically wherever that type appears.bytes token rather than a nested CBOR structure).The default Tagged behaviour is itself implemented as a typeEncoders entry under the type name 'Tagged'. You can override it by supplying your own typeEncoders.Tagged and returning null from it to fall through to the default for cases your override doesn't want to handle.
The typeEncoders property to the options argument to encode() allows you to add additional functionality to cborg, or override existing functionality.
When converting JavaScript objects, types are differentiated using the method and naming used by @sindresorhus/is (a custom implementation is used internally for performance reasons) and an internal set of type encoders are used to convert objects to their appropriate CBOR form. Supported types are: null, undefined, number, bigint, string, boolean, Array, Object, Map, Buffer, ArrayBuffer, DataView, Uint8Array and all other TypedArrays (their underlying byte array is encoded, so they will all round-trip as a Uint8Array since the type information is lost). Any object that doesn't match a type in this list will cause an error to be thrown during decode. e.g. encode(new Date()) will throw an error because there is no internal Date type encoder.
The typeEncoders option is an object whose property names match to @sindresorhus/is type names. When this option is provided and a property exists for any given object's type, the function is called with the signature (obj, typ, options, refStack):
obj - the value being encodedtyp - the resolved type name (the same string used as the property key)options - the full encode options object, useful for recursive encoding (see below)refStack - an internal circular-reference tracker, opaque to user code, that should be threaded through any recursive callIf a type encoder function returns null, the default encoder, if any, is used instead.
If a type encoder function returns an array, cborg will expect it to contain zero or more Token objects (or nested arrays thereof) that will be encoded to binary form. To recursively encode a nested JavaScript value as part of your tokens (so that typeEncoders for the nested value still apply), import objectToTokens from cborg and call objectToTokens(value, options, refStack) from inside your encoder. This is how the built-in Tagged encoder, mapEncoder, and setEncoder recurse into their contents:
import { Token, Type, objectToTokens } from 'cborg'
function myWrapperEncoder (obj, _typ, options, refStack) {
return [
new Token(Type.tag, 1234),
objectToTokens(obj.inner, options, refStack)
]
}
For tag encoders that flatten their content to a leaf type (e.g. encode a BigInt as a Type.bytes token under tag 2), you don't need to recurse - just return the flat token sequence directly, as the bigIntEncoder example below does.
Tokens map directly to CBOR entities. Each one has a Type and a value. A type encoder is responsible for turning a JavaScript object into a set of tags.
This example is available from the cborg taglib as bigIntEncoder (import { bigIntEncoder } as taglib from 'cborg/taglib') and implements CBOR tags 2 and 3 (bigint and negative bigint). This function would be registered using an options parameter { typeEncoders: { bigint: bigIntEncoder } }. All objects that have a type bigint will pass through this function.
import { Token, Type } from './cborg.js'
function bigIntEncoder (obj) {
// check whether this BigInt could fit within a standard CBOR 64-bit int or less
if (obj >= -1n * (2n ** 64n) && obj <= (2n ** 64n) - 1n) {
return null // handle this as a standard int or negint
}
// it's larger than a 64-bit int, encode as tag 2 (positive) or 3 (negative)
return [
new Token(Type.tag, obj >= 0n ? 2 : 3),
new Token(Type.bytes, fromBigInt(obj >= 0n ? obj : obj * -1n - 1n))
]
}
function fromBigInt (i) { /* returns a Uint8Array, omitted from example */ }
This example encoder demonstrates the ability to pass-through to the default encoder, or convert to a series of custom tags. In this case we can put any arbitrarily large BigInt into a byte array using the standard CBOR tag 2 and 3 types.
Valid Token types for the second argument to Token() are:
Type.uint
Type.negint
Type.bytes
Type.string
Type.array
Type.map
Type.tag
Type.float
Type.false
Type.true
Type.null
Type.undefined
Type.break
Using type encoders we can:
Tokens)null as a pass-through)numbers into floats)undefined)By default cborg does not support decoding of any tags. Where a tag is encountered during decode, an error will be thrown. If tag support is needed, they will need to be supplied as options to the decode() function. The tags property should contain an object mapping tag numbers to decoder functions.
Tag decoder functions receive a decode control object with two methods:
decode(): decode the tagged content and return itdecode.entries(): for map content, returns [[key, value], ...] preserving key typesThis example is available from the cborg taglib as bigIntDecoder and bigNegIntDecoder (import { bigIntDecoder, bigNegIntDecoder } from 'cborg/taglib') and implements CBOR tags 2 and 3 (bigint and negative bigint). This function would be registered using an options parameter:
const tags = {
2: bigIntDecoder,
3: bigNegIntDecoder
}
decode(bytes, { tags })
Implementation:
function bigIntDecoder (decode) {
const bytes = decode() // get the tagged byte content
let bi = 0n
for (let ii = 0; ii < bytes.length; ii++) {
bi = (bi << 8n) + BigInt(bytes[ii])
}
return bi
}
function bigNegIntDecoder (decode) {
const bytes = decode()
let bi = 0n
for (let ii = 0; ii < bytes.length; ii++) {
bi = (bi << 8n) + BigInt(bytes[ii])
}
return -1n - bi
}
For tags that wrap CBOR maps and need to preserve non-string key types, use decode.entries():
// Tag 259: Map with any key type
function mapDecoder (decode) {
return new Map(decode.entries())
}
decode() allows overriding the tokenizer option to provide a custom tokeniser. This object can be described with the following interface:
export interface DecodeTokenizer {
next(): Token,
done(): boolean,
pos(): number,
}
next() should return the next token in the stream, done() should return true when the stream is finished, and pos() should return the current byte position in the stream.
Overriding the default tokeniser can be useful for changing the rules of decode. For example, it is used to turn cborg into a JSON decoder by changing parsing rules on how to turn bytes into tokens. See the source code for how this works.
The default Tokenizer class is available from the default export. Providing options.tokenizer = new Tokenizer(bytes, options) would result in the same decode path using this tokeniser. However, this can also be used to override or modify default decode paths by intercepting the token stream. For example, to perform a decode that disallows bytes, the following code would work:
import { decode, Tokenizer, Type } from 'cborg'
class CustomTokeniser extends Tokenizer {
next () {
const nextToken = super.next()
if (Type.equals(nextToken.type, Type.bytes)) {
throw new Error('Unsupported type: bytes')
}
return nextToken
}
}
function customDecode (data, options) {
options = Object.assign({}, options, {
tokenizer: new CustomTokeniser(data, options)
})
return decode(data, options)
}
cborg is designed with deterministic encoding forms as a primary feature. It is suitable for use with content addressed systems or other systems where convergence of binary forms is important. The ideal is to have strictly one way of mapping a set of data into a binary form. Unfortunately CBOR has many opportunities for flexibility, including:
1 may be encoded as 0x01, 0x1801, 0x190001, 1a00000001 or 1b0000000000000001.1. Tags can also vary in size and still represent the same number.NaN, Infinity and -Infinity to be represented in many different ways, meaning it is possible to represent the same data using many different byte forms.By default, cborg will always encode objects to the same bytes by applying some strictness rules:
rfc8949EncodeOptions to apply this rule).number differentiation - if a fractional part is missing and it's within the safe integer boundary, it's encoded as an integer, otherwise it's encoded as a float.By default, cborg allows for some flexibility on decode of objects, which will present some challenges if users wish to impose strictness requirements at both serialisation and deserialisation. Options that can be provided to decode() to impose some strictness requirements are:
strict: true to impose strict sizing rules for int, negative ints and lengths of lengthed objectsallowNaN: false and allowInfinity to prevent decoding of any value that would resolve to NaN, Infinity or -Infinity, using CBOR tokens or IEEE 754 representationβas long as your application can do without these symbols.allowIndefinite: false to disallow indefinite lengthed objects and the "break" tagRFC 8949 updates the canonical map ordering recommendation to plain bytewise comparisons. The rfc8949EncodeOptions export configures cborg to follow this rule and can be passed directly to encode:
import { encode, rfc8949EncodeOptions } from 'cborg'
const bytes = encode(obj, rfc8949EncodeOptions)
You can also merge these defaults with your own preferences:
import { encode, rfc8949EncodeOptions } from 'cborg'
const bytes = encode(obj, { ...rfc8949EncodeOptions, typeEncoders: YOUR_TYPE_ENCODERS })
Currently, there are two areas that cborg cannot impose strictness requirements (pull requests welcome!):
There are a number of forms where an object will not round-trip precisely, if this matters for an application, care should be taken, or certain types should be disallowed entirely during encode.
TypedArrays will decode as Uint8Arrays, unless a custom tag is used.Map and Object will be encoded as a CBOR map, as will any other object that inherits from Object that can't be differentiated by the @sindresorhus/is algorithm. They will all decode as Object by default, or Map if useMaps is set to true. e.g. { foo: new Map() } will round-trip to { foo: {} } by default.cborg can also encode and decode JSON using the same pipeline and many of the same settings. For most (but not all) cases it will be faster to use JSON.parse() and JSON.stringify(), however cborg provides much more control over the process to handle determinism and be more restrictive in allowable forms. It also operates natively with Uint8Arrays rather than strings which may also offer some minor efficiency or usability gains in some circumstances.
Use import { encode, decode, decodeFirst } from 'cborg/json' to access the JSON handling encoder and decoder.
Many of the same encode and decode options available for CBOR can be used to manage JSON handling. These include strictness requirements for decode and custom tag encoders for encode. Tag encoders can't create new tags as there are no tags in JSON, but they can replace JavaScript object forms with custom JSON forms (e.g. convert a Uint8Array to a valid JSON form rather than having the encoder throw an error). The inverse is also possible, turning specific JSON forms into JavaScript forms, by using a custom tokeniser on decode.
Special notes on options specific to the JSON:
allowBigInt option: is repurposed for the JSON decoder and defaults to false. When false, all numbers are decoded as Number, possibly losing precision when encountering numbers outside of the JavaScript safe integer range. When true numbers that have a decimal point (., even if just .0) are returned as a Number, but for numbers without a decimal point and that are outside of the JavaScript safe integer range, they are returned as BigInts. This behaviour differs from CBOR decoding which will error when decoding integer and negative integer tokens that are outside of the JavaScript safe integer range if allowBigInt is false.See @ipld/dag-json for an advanced use of the cborg JSON encoder and decoder including round-tripping of Uint8Arrays and custom JavaScript classes (IPLD CID objects in this case).
Similar to the CBOR example above, using JSON:
import { encode, decode } from 'cborg/json'
const decoded = decode(Buffer.from('7b2274686973223a7b226973223a224a534f4e21222c22796179223a747275657d7d', 'hex'))
console.log('decoded:', decoded)
console.log('encoded:', encode(decoded))
console.log('encoded (string):', Buffer.from(encode(decoded)).toString())
decoded: { this: { is: 'JSON!', yay: true } }
encoded: Uint8Array(34) [
123, 34, 116, 104, 105, 115, 34, 58,
123, 34, 105, 115, 34, 58, 34, 74,
83, 79, 78, 33, 34, 44, 34, 121,
97, 121, 34, 58, 116, 114, 117, 101,
125, 125
]
encoded (string): {"this":{"is":"JSON!","yay":true}}
As demonstrated above, the ability to provide custom typeEncoders to encode(), tags and even a custom tokenizer to decode() allow for quite a bit of flexibility in manipulating both the encode and decode process. An advanced example that uses all of these features can be found in example-bytestrings.js which demonstrates how one might implement RFC 8746 to allow typed arrays to round-trip through CBOR and retain their original types. Since cborg is designed to speak purely in terms of Uint8Arrays, its default behaviour will squash all typed arrays down to their byte array forms and materialise them as plain Uint8Arrays. Where round-trip fidelity is important and CBOR tags are an option, this form of usage is an option.
cborg/extended)Need to serialise Date, Map, Set, RegExp, BigInt, Error, or TypedArray? The cborg/extended module provides encode/decode with built-in support for native JavaScript types that JSON can't handle.
The type support is similar to the browser's structured clone algorithm, the built-in deep-copy mechanism that handles these same types. cborg/extended provides similar type fidelity in a compact binary serialisation format.
See example-extended.js for a runnable demo.
import { encode, decode } from 'cborg/extended'
const data = {
date: new Date(),
pattern: /foo.*bar/gi,
mapping: new Map([['key', 'value'], [42, 'number key']]),
collection: new Set([1, 2, 3]),
binary: new Uint16Array([1, 2, 3]),
bignum: 12345678901234567890n,
error: new TypeError('something went wrong')
}
const encoded = encode(data)
const decoded = decode(encoded)
// All types are preserved
decoded.date instanceof Date // true
decoded.pattern instanceof RegExp // true
decoded.mapping instanceof Map // true
decoded.collection instanceof Set // true
decoded.binary instanceof Uint16Array // true
typeof decoded.bignum === 'bigint' // true
decoded.error instanceof TypeError // true
cborg/extendedUse cborg/extended instead of base cborg or JSON when you need:
toISOString()/new Date() conversionnew Map([[1, 'one'], [{}, 'object key']]) just worksFloat32Array stays Float32Array, not Uint8ArrayError, TypeError, RangeError, etc. preserve type and message-0 round-trips correctly (base cborg encodes as 0)| Type | CBOR Tag | Notes |
|---|---|---|
Date | 1 | Epoch seconds as float (millisecond precision) |
RegExp | 21066 | Pattern and flags preserved |
Set | 258 | IANA registered finite set |
Map | 259 | Supports any key type |
BigInt | 2, 3 | Always tagged for round-trip fidelity |
Error | 27 | All standard error types (TypeError, RangeError, etc.) |
-0 | (float) | Negative zero encoded as half-precision float |
Uint8Array | 64 | RFC 8746 |
Uint8ClampedArray | 68 | RFC 8746 |
Int8Array | 72 | RFC 8746 |
Uint16Array | 69 | RFC 8746 (little-endian) |
Int16Array | 77 | RFC 8746 (little-endian) |
Uint32Array | 70 | RFC 8746 (little-endian) |
Int32Array | 78 | RFC 8746 (little-endian) |
Float32Array | 85 | RFC 8746 (little-endian) |
Float64Array | 86 | RFC 8746 (little-endian) |
BigUint64Array | 71 | RFC 8746 (little-endian) |
BigInt64Array | 79 | RFC 8746 (little-endian) |
cborg/extended preserves the distinction between plain objects and Map instances:
// Plain objects round-trip as plain objects
const obj = { name: 'Alice', age: 30 }
decode(encode(obj)) // { name: 'Alice', age: 30 }
// Maps round-trip as Maps (including non-string keys)
const map = new Map([[1, 'one'], ['two', 2]])
decode(encode(map)) // Map { 1 => 'one', 'two' => 2 }
// Mixed structures work correctly
const data = {
users: new Map([['alice', { role: 'admin' }]])
}
const decoded = decode(encode(data))
decoded.users instanceof Map // true
decoded.users.get('alice') // { role: 'admin' } (plain object)
This works because Map instances are encoded with CBOR Tag 259, while plain objects use untagged CBOR maps. The decoder uses decode.entries() internally to preserve key types for tagged maps.
The tags used by cborg/extended are standard CBOR tags registered with IANA:
Important considerations:
Parser support varies: CBOR parsers that don't recognise these tags will either error or return raw tagged values. Many minimal CBOR implementations only handle core types. Test interoperability with your specific target platforms.
Not for content addressing: cborg/extended prioritises JavaScript type fidelity over deterministic encoding. Map and object keys preserve insertion order (not sorted), floating-point dates lose sub-millisecond precision, and Set iteration order depends on insertion. The same data structure built differently may encode to different bytes. For content-addressed systems (IPLD, CIDs), use base cborg with @ipld/dag-cbor conventions instead.
Implementation differences: Even among parsers that support these tags, behaviour may differ. For example, Date precision (seconds vs milliseconds), RegExp flag handling, or TypedArray endianness assumptions. The CBOR specs allow flexibility that can cause subtle incompatibilities.
JavaScript-centric: Types like RegExp and JavaScript's specific TypedArray variants don't have equivalents in many languages. Data encoded with cborg/extended is best suited for JavaScript-to-JavaScript communication.
cborg/taglib)If you don't need all extended types, cborg/taglib exports individual encoders and decoders. Use this when you want to enable specific types without the full cborg/extended configuration, or when you need to customise behaviour.
Tip: See lib/extended/extended.js for how
cborg/extendedassembles the taglib components, use it as a template for your own configuration.
import { encode, decode } from 'cborg'
import {
dateEncoder,
dateDecoder,
bigIntEncoder,
bigIntDecoder,
bigNegIntDecoder,
TAG_DATE_EPOCH
} from 'cborg/taglib'
// Enable just Date and BigInt support
const encoded = encode(data, {
typeEncoders: {
Date: dateEncoder,
bigint: bigIntEncoder
}
})
const decoded = decode(encoded, {
tags: {
[TAG_DATE_EPOCH]: dateDecoder,
2: bigIntDecoder,
3: bigNegIntDecoder
}
})
cborg/extended vs cborg/taglib| Use case | Module |
|---|---|
| Serialize all JS types, minimal config | cborg/extended |
| Only need Date and BigInt | cborg/taglib with selective imports |
| Custom encode/decode logic | cborg/taglib as building blocks |
| Interop with IPLD/content-addressed systems | cborg/taglib with bigIntEncoder (not structBigIntEncoder) |
Encoders:
dateEncoder: Date as epoch float (Tag 1)regExpEncoder: RegExp as [pattern, flags] (Tag 21066)setEncoder: Set as tagged array (Tag 258)mapEncoder: Map as tagged CBOR map (Tag 259)bigIntEncoder: BigInt, only tags values outside 64-bit range (IPLD compatible)structBigIntEncoder: BigInt, always tags (full round-trip as bigint)uint8ArrayEncoder, uint8ClampedArrayEncoder, int8ArrayEncoder, uint16ArrayEncoder, int16ArrayEncoder, uint32ArrayEncoder, int32ArrayEncoder, float32ArrayEncoder, float64ArrayEncoder, bigUint64ArrayEncoder, bigInt64ArrayEncoderDecoders:
dateDecoder, regExpDecoder, setDecoder, mapDecoderbigIntDecoder (Tag 2), bigNegIntDecoder (Tag 3)uint8ArrayDecoder, uint8ClampedArrayDecoder, int8ArrayDecoder, uint16ArrayDecoder, int16ArrayDecoder, uint32ArrayDecoder, int32ArrayDecoder, float32ArrayDecoder, float64ArrayDecoder, bigUint64ArrayDecoder, bigInt64ArrayDecoderTag constants: TAG_DATE_STRING, TAG_DATE_EPOCH, TAG_BIGINT_POS, TAG_BIGINT_NEG, TAG_UINT8_ARRAY, TAG_UINT8_CLAMPED_ARRAY, TAG_INT8_ARRAY, TAG_UINT16_ARRAY_LE, TAG_INT16_ARRAY_LE, TAG_UINT32_ARRAY_LE, TAG_INT32_ARRAY_LE, TAG_FLOAT32_ARRAY_LE, TAG_FLOAT64_ARRAY_LE, TAG_BIGUINT64_ARRAY_LE, TAG_BIGINT64_ARRAY_LE, TAG_SET, TAG_MAP, TAG_REGEXP
Copyright 2020 Rod Vagg
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.