Tag 39649 (shaped array) is provisional: boring emits it today under
:shapes true, but it is not in the IANA CBOR tag registry, so the number is
not reserved and another registrant could take it.
This file is the request, ready to send. It is written out rather than described because the registration is a one-shot outward-facing act and the exact wording is what ends up permanently in a public registry.
doc/COMPATIBILITY.md promises tag assignments are append-only. That promise
starts binding at the first release, so the number has to be either registered
or removed before then — leaving it unregistered means publishing a claim on a
number someone else may already be filing for.
The original choice was 40000. The registry had it already assigned, to
ur:known-value (Blockchain Commons) — verified again on 2026-07-31, along
with a dense ur: cluster running from 40000 to 40918. 39649 was picked to be
hard to collide with rather than tidy: odd and non-round, because registrants
take round numbers and contiguous blocks, and mid-gap in 39000–39999, which is
still entirely unassigned.
Re-verified against https://www.iana.org/assignments/cbor-tags/tags.csv on 2026-07-31: 39649 unassigned, nothing assigned in 38000–39999.
Only one number is claimed. The offset index was briefly tag 39651 and is
now a tag-27 name, boring/index — tag 27 being CBOR's registered extension
point for named types, needing no number of its own. That decision was measured
rather than argued: the index occurs once per file, so a name costs 0.05%.
Tag 39649 stays a number because shaped arrays occur per array. Measured, carrying them as a tag-27 name instead would add 19.6% to a document of 100 small tables and 35.2% to one of 500 — on a feature whose whole purpose is to shrink exactly those. That is the rationale, recorded here because "we could not remember why" is a poor answer to give during registration.
Tags 32768 and above are First Come First Served (RFC 8949 §9.2): no expert review, no RFC, no standards action. Submit via https://www.iana.org/form/protocol-assignment selecting the Concise Binary Object Representation (CBOR) Tags registry, or email iana@iana.org.
FCFS still requires a permanent, publicly reachable specification URL. Fill
in <SPEC-URL> below with the published location of doc/SHAPES.md before
sending; a URL that later 404s is worse than no registration, so prefer a
tagged-release permalink over a branch URL.
Please register the following value in the Concise Binary Object Representation (CBOR) Tags registry.
CBOR tag: 39649
Data item: array
Semantics: Shaped array; array of maps with the key set hoisted out, encoded as
[keys, [values-per-row, ...]], whereundefinedin a value position and a row shorter than the key set both mean the corresponding key is absent from that mapReference:
<SPEC-URL>Point of contact: Christian Weilbach ch_weil@topiq.es
The tag content is a two-element array:
value [{:e 1 :a :x} {:e 2 :a :y}]
encoded d9 9ae1 tag 39649
82 array(2)
82 the key set, once
d827 62 3a65 ":e"
d827 62 3a61 ":a"
82 the rows
82 01 d827623a78 row 1: values only
82 02 d827623a79 row 2: values only
A row need not carry every key. The key set is the union across rows, and absence has two spellings:
undefined (simple value 23) in a value position: that key is absent
from this map.value [{:a nil :b 1} {:b 2}]
encoded d9 9ae1 82
82 d827623a61 d827623a62 keys :a :b
82
82 f6 01 :a PRESENT, value null
82 f7 02 :a ABSENT
null (simple value 22) is not absence. It is a key that is present with a
null value, and {:a null} and {} are different maps.
Distinguishing simple values 22 and 23 is therefore a conformance requirement of this tag. RFC 8949 §3.3 already makes them distinct data items; an implementation that maps both to one host value cannot implement this tag correctly and should decline it rather than report an absent key as present. The marker appears only inside this tag's content, so a decoder that does not implement the tag never encounters it.
Surveyed at time of writing, decoding
39649([["a","b"],[[null,1],[undefined,2]]]): cbor2 (Python), cbor-x and
cbor (JavaScript) and clj-cbor all keep the two apart and preserve the tag.
ciborium (Rust) preserves the tag but collapses the marker at its Value
layer, so an implementer must decode below it. See doc/INTEROP.md.
A row longer than the key set is malformed: there is no key for the extra value to bind to, and a decoder must reject the item rather than truncate. Keys must be distinct. Both are enforced by boring's readers on each platform.
The undefined convention is taken deliberately from
draft-ietf-cbor-packed, whose tag-114 record function spells
absence the same way, so that an implementation of one informs the other.
A decoder that does not implement the tag surfaces an opaque tagged value
rather than misreading, and the tag is only emitted when the caller opts in
with :shapes true, so an unregistered collision degrades to "unrecognised
extension" and never to silent corruption.
Not a general packing mechanism. draft-ietf-cbor-packed covers that ground
with a document-scoped table, and its tag 114 record function is the same
idea generalised. boring deliberately did not adopt it for the first
release — see doc/COMPATIBILITY.md — so 39649 stands on its own as a narrow,
self-contained construct: one array, keys hoisted once, no table, no
references, no expansion.
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |