Lace / docs

Lace-310 · JSONqa Query Attributes

Tags: browser, jsonqa, encoding, dx

Purpose

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.

JSONqa is an ordered string-leaf data model

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.

Source form

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.

Strings may be bare or quoted

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")

Duplicate names are last-wins

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 serialization has one text form

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.

Equality compares values, not source spelling

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.

Address suffix extraction is structural

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.

Reserved attributes belong to embedding profiles

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.

Rejection examples

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