Tags: browser, jsonqa, encoding, dx
Browser addresses need structured application and navigation state that does not become record identity. JSONqa is a compact ordered text format appended to an address. This specification defines its data model, source grammar, parsing, and canonical serialization independently of any one address scheme.
300 defines how Lace browser addresses attach JSONqa and assigns the
address shape of reserved #, via, and
by attributes. 320 defines the limited projection between
JSONqa and ordinary HTTP query/fragment state.
A JSONqa value is one of:
String(text)
Array(value...)
Object(attribute...)
Every leaf is a string. JSONqa has no number, boolean, or null type:
{page:5} -> String("5")
{enabled:true} -> String("true")
{draft} -> String("")
Objects preserve attribute order. Arrays preserve item order. Attribute names and string values are UTF-8 NFC text and MUST NOT contain LF, CR, C0 control characters, or DEL.
A complete JSONqa object is enclosed in { and
}:
{}
{page:5}
{#:section}
{tags:[a,b,c]}
{opts:{dark:1}}
{draft}
{name:'hello world'}
{via:'wss:example.test/interlace'}
The grammar is:
jsonqa = object
object = "{" ws [attribute *(ws "," ws attribute) [ws ","]] ws "}"
attribute = string [ws ":" ws value]
value = string / array / object
array = "[" ws [value *(ws "," ws value) [ws ","]] ws "]"
string = bare / single-quoted / double-quoted
ws = *(SP / HTAB)
An attribute without : has the empty string value. Empty
arrays and objects are valid. Source accepts one trailing comma in an
object or array. Canonical text never emits a trailing comma or
insignificant whitespace.
A parser MUST consume the complete input except surrounding SP/HTAB.
Extra text following the closing } is invalid.
A bare string MUST contain at least one scalar value. A bare
attribute name ends before unescaped SP, HTAB, :,
{, }, [, ],
,, ", or '.
A bare value ends before unescaped SP, HTAB, {,
}, [, ], ,,
", or '. Colons after the attribute’s first
separator colon are ordinary bare-value data, so this source is
valid:
{via:wss:example.test/interlace}
Single-quoted and double-quoted strings preserve all allowed text until the matching unescaped quote. Source accepts these escapes in bare or quoted text:
\{ \} \[ \] \, \: \\ \" \'
A backslash followed by any other character is invalid. Quotes do not
interpret JSON escapes such as \n, \uXXXX, or
\/; JSONqa is not JSON.
Examples:
{key:a\:b} -> String("a:b")
{name:'hello world'} -> String("hello world")
{quote:"a\"b"} -> String("a\"b")
Within one object, a later attribute with the same decoded name replaces the earlier value:
{a:1,b:2,a:3} -> {a:3,b:2}
Replacement retains the attribute’s first position in object order. Duplicate handling applies recursively to nested objects.
Profiles that assign security meaning to an attribute operate on the final last-wins value. They MUST NOT inspect an earlier duplicate as a separate instruction.
Canonical JSONqa text is UTF-8 NFC and has:
A non-empty string is emitted bare only when none of its scalar values is whitespace or one of:
{ } [ ] , : \ " '
Otherwise it is enclosed in double quotes. Inside canonical double
quotes, only " and \ are escaped. Other
allowed characters, including {, },
[, ], ,, :, and
', remain literal inside the quotes.
An empty string used as an object attribute value is canonically
represented by omitting : and the value:
{draft}
An empty string used as an array item is "".
Examples of source normalization:
{ page : 5, } -> {page:5}
{name:'hello world'} -> {name:"hello world"}
{via:wss:example/interlace} -> {via:"wss:example/interlace"}
{key:a\:b} -> {key:"a:b"}
{a:1,a:2} -> {a:2}
The standalone canonical form of an empty object is {}.
A profile embedding JSONqa as an optional suffix MAY omit an empty
top-level object; 300 omits it in canonical browser URCs.
Two JSONqa values are equal when they have the same recursive value kinds, strings, array order, object attribute order, names, and values after duplicate reduction. Quote style, escapes, whitespace, and trailing commas do not affect semantic equality.
Canonical serialization of equal values produces identical bytes.
A profile may append one complete JSONqa object to another address grammar. The profile MUST identify the suffix before applying a generic URL query or fragment parser.
For the 300 browser-address profile, raw { and
} are forbidden in decoded 010 coordinate fields. The first
raw { after the encoded coordinate therefore starts JSONqa,
and its matching top-level } MUST be the final address
byte. Braces inside quoted strings or nested objects are handled by this
grammar and do not close the top-level object.
A %7B byte sequence is ordinary text for an enclosing
percent-decoding profile until that profile says otherwise. JSONqa
itself does not percent-decode.
JSONqa does not assign universal behavior to attribute names. An embedding profile may reserve names and validate their value kinds. Under 300:
# scalar string
via scalar string
by scalar canonical 009 verification-key string
Application attributes share the ordered object but MUST NOT override
the profile meaning of reserved attributes. Parsing a via
value, including wss:, creates no peer authority; 060
connected proof establishment owns that result.
Implementations reject unterminated strings, missing braces or
brackets, invalid escapes, disallowed control text, non-NFC strings,
missing values after :, extra trailing text, and malformed
separators. Examples:
page:5}
{page:5
{tags:[a,b}
{key:\q}
{key:}
{a:1 b:2}
{a:1}trailing