8. Tags
Readable Object Notation
Tags are shorthand for boolean-valued members of a map.
Tags form a set of string names, each associated with a boolean value.
They are not general-purpose sets of arbitrary values: names are represented as JSON object keys, which can only be strings.
A tag group encloses key names between < and >.
A name without a prefix gives that key the value true.
An immediately preceding ! gives it the value false.
Tags introduce no new value type and are distinct from the #-prefixed objects called tagged elements below.
<foo bar !baz> name Ada
<bar !baz foo> name Ada
{
"foo": true,
"bar": true,
"baz": false,
"name": "Ada"
}
The formatter collects all boolean members into one group before the other members. It sorts tags by their key names, without including the negation marker in the sort. The remaining members follow in key-name order.
Ordinary boolean members remain valid input. Input may contain multiple tag groups anywhere an object member can begin, including before, between, or after ordinary members. For example:
<foo> name Ada age 36 baz false <bar>
<bar !baz foo> age 36 name Ada
{
"foo": true,
"name": "Ada",
"age": 36,
"baz": false,
"bar": true
}
Tag groups also balance names across rows when they exceed the width target. A group with attached comments uses separate lines so each comment stays with its name:
<configuration development documentation experimental instrumentation observability performance production validation>
<configuration development documentation experimental instrumentation observability performance production validation>
{
"configuration": true,
"development": true,
"documentation": true,
"experimental": true,
"instrumentation": true,
"observability": true,
"performance": true,
"production": true,
"validation": true
}
<tag00_longname tag01_longname tag02_longname tag03_longname tag04_longname tag05_longname tag06_longname tag07_longname tag08_longname tag09_longname tag10_longname>
<tag00_longname tag01_longname tag02_longname tag03_longname tag04_longname tag05_longname tag06_longname tag07_longname tag08_longname tag09_longname tag10_longname>
{
"tag00_longname": true,
"tag01_longname": true,
"tag02_longname": true,
"tag03_longname": true,
"tag04_longname": true,
"tag05_longname": true,
"tag06_longname": true,
"tag07_longname": true,
"tag08_longname": true,
"tag09_longname": true,
"tag10_longname": true
}
Duplicate names are errors both within a group and across groups or ordinary members.
For example, <foo !foo> and {<foo> foo false} are invalid.
Table of Contents
8.1. Names and quoting ↑
Tag names are strings, including spellings such as true, null, or 42.
Names are separated by whitespace, even when the group spans lines.
Commas do not separate tags.
The closing > ends the group rather than becoming part of a bare name.
Use ordinary key quoting for names containing whitespace or delimiters, or beginning with a literal !.
The negation marker goes outside the quoted name:
<"display name" !"not active" "!literal">
<"!literal" "display name" !"not active">
{
"display name": true,
"not active": false,
"!literal": true
}
The literal ! in "!literal" is part of the key and participates in sorting.
The negation marker before "not active" does not.
An empty key can be written "", or !"" for a false value.
A negation marker without a name is invalid.
A tag group is recognized only where an object member can begin.
An ordinary object key beginning with < must be quoted.
Inside a bare string, after its first character, < and > are ordinary text, as in a<b.
8.2. Tag-only maps ↑
A tag group may also stand alone in value position: in a list, as a member value, or as a tagged element's payload.
There it is a whole map whose members are its tags, so <a !b> reads as {a true b false}.
The formatter writes every nonempty map whose members are all booleans this way, without braces.
A map with any other member keeps its braces, and the empty map stays {}.
list [{<b a>} {x true y false} {}] member {active true} nested {outer {inner {<on>}}} prefixed #err {<integer overflow>} mixed {<a> n 1}
list [<a b> <x !y> {}] member <active> mixed {<a> n 1} nested {outer {inner <on>}} prefixed #err <integer overflow>
{
"list": [
{
"b": true,
"a": true
},
{
"x": true,
"y": false
},
{}
],
"member": {
"active": true
},
"nested": {
"outer": {
"inner": {
"on": true
}
}
},
"prefixed": {
"#err": {
"integer": true,
"overflow": true
}
},
"mixed": {
"a": true,
"n": 1
}
}
list [<b a> <!c>] member <active> prefixed #err <overflow integer> nested [[<a>] {k <b>}]
list [<a b> <!c>] member <active> nested [[<a>] {k <b>}] prefixed #err <integer overflow>
{
"list": [
{
"b": true,
"a": true
},
{
"c": false
}
],
"member": {
"active": true
},
"prefixed": {
"#err": {
"overflow": true,
"integer": true
}
},
"nested": [
[
{
"a": true
}
],
{
"k": {
"b": true
}
}
]
}
A string that begins with < is therefore quoted:
quoted ["<foo" "<" "<=" a<b]
quoted ["<foo" "<" "<=" a<b]
{
"quoted": [
"<foo",
"<",
"<=",
"a<b"
]
}
A tag-only map ends on the line where it opens. An empty group <> is invalid, since {} already writes the empty map.
8.3. Nested and empty objects ↑
Tags work in every object, including nested objects and objects containing only boolean members:
person { <active !admin> name Ada } flags {<readable !writable>}
flags <readable !writable> person {<active !admin> name Ada}
{
"person": {
"active": true,
"admin": false,
"name": "Ada"
},
"flags": {
"readable": true,
"writable": false
}
}
A tags-only document such as <active> represents an implicit outer object, just as ordinary members do.
It is already in canonical form.
An empty group <> contributes no members and is omitted by the formatter.
An object with no members formats as {}.
For example, this input:
8.4. Comments ↑
Tag groups may span lines and contain comments using the ordinary comment rules.
Comments are stored in the enclosing object's $comment member, using the key name without the negation marker.
Formatting preserves whether a comment appears before a tag or inline after it, and keeps it with that key when tags are sorted.
When the first tag has no leading comment, canonical output places it
immediately after <; later tags and their comments align beneath it. If a
comment must precede the first tag, < stays on a line of its own. A closing
> follows the final tag unless a trailing comment requires it on the next line.
For example, this input:
< !admin ; Access restrictions active >
<active !admin ; Access restrictions >
{
"$comment": "admin: Access restrictions",
"admin": false,
"active": true
}
If an earlier tag has a trailing comment and the final tag does not, the
closing > follows the final tag:
< !admin active ; Enabled by default >
<active ; Enabled by default !admin>
{
"$comment": "active: Enabled by default",
"admin": false,
"active": true
}
A comment on a separate line immediately before !admin instead uses admin:: Access restrictions.
< ; Enabled by default active !admin >
< ; Enabled by default active !admin>
{
"$comment": "active:: Enabled by default",
"active": true,
"admin": false
}