Skip to content

Encoder quotes non-ASCII keys only when they occur at position 0 (§7.3) #65

Description

@antrixy

SPEC Version

v4.1

SPEC Section

Section 7.3: Key Encoding

SPEC Requirement

§7.3 states that object keys and tabular field names MAY be unquoted only if they match ^[A-Za-z_][A-Za-z0-9_.]*$, and otherwise MUST be quoted and escaped per §7.1.

The pattern is ASCII-only and applies to the whole key, not just its first character. A non-ASCII character anywhere in the key takes it outside the pattern, so the key must be quoted.

This wording is unchanged between v3.0 and v4.1, so the finding does not depend on which version the package targets. v4.1's §16 spells the consequence out: conforming encoders quote every non-ASCII key.

Current Behavior

encode quotes a non-ASCII key when the non-ASCII character is FIRST, but leaves it unquoted when it occurs anywhere later.

Against toon_format 0.9.0b1:

input key non-ASCII at output
a-b "a-b": 1 — correct
éabc 0 "éabc": 1 — correct
日abc 0 "日abc": 1 — correct
aébc 1 aébc: 1
a日bc 1 a日bc: 1
abcé last abcé: 1
1 _é: 1
a_é_b 2 a_é_b: 1

Behaviour is identical for BMP (é) and CJK () at both positions, so it is positional rather than an encoding-width issue.

The head character appears to be validated against §7.3's head class correctly, while the remaining characters are checked with an ASCII-blind test. is the clearest case: _ is a legal head character, so the head check passes and the tail check never rejects the é.

Test Case

toon_format 0.9.0b1

from toon_format import encode

for k in ["a-b", "éabc", "日abc", "aébc", "a日bc", "abcé", "_é", "a_é_b"]:
print(repr(encode({k: 1})))

Expected (matches @toon-format/toon 2.3.0, which quotes all of these):

'"a-b": 1'

'"éabc": 1'

'"日abc": 1'

'"aébc": 1'

'"a日bc": 1'

'"abcé": 1'

'"_é": 1'

'"a_é_b": 1'

Actual:

'"a-b": 1' <- correct

'"éabc": 1' <- correct

'"日abc": 1' <- correct

'aébc: 1'

'a日bc: 1'

'abcé: 1'

'_é: 1'

'a_é_b: 1'

Additional Context

Why this survives round-trip tests: §7.4 requires decoders to accept any unquoted key token as a literal key, even one §7.3 forbids an encoder from emitting. So the package decodes its own output correctly and self-consistency tests pass. It surfaces only against another implementation — @toon-format/toon 2.3.0 rejects the output in strict mode.

The positional nature is likely why it hasn't been caught: a fixture with a leading non-ASCII key — the obvious one to write — passes.

Scope tested: object keys at positions 0, 1, 2 and last, across BMP and CJK. Not tested: tabular field names, entry keys in keyed tabular form (§9.5), or keys that would require quoting for an additional §7.2 reason.

A related encoder issue was filed against toon-rust as toon-format/toon-rust#77; the mechanism there differs (no position is quoted).

Found with toon-diff (https://github.com/antrixy/toon-diff), a differential conformance fuzzer that round-trips generated values through the TypeScript, Python and Rust implementations against a lossless oracle. It generates values from the JSON grammar rather than from fixtures, which is how a non-leading non-ASCII key came up.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions