Skip to content

Nested arrays fail to round-trip: encoder emits a length-1 list-array header over an empty body ([[[]]]) #62

Description

@antrixy

Description

toon-python (0.9.0b1) mishandles nested array-of-array values in both directions. The reference implementation @toon-format/toon (v2.3.0) round-trips [[[]]] correctly, so both facets are specific to toon-python.

Encode: encode([[[]]]) produces a list-array header ([1]:) that claims one item over a body that yields none, so it cannot be decoded — even by toon-python's own decoder. encode([[[]]]) produces:

[1]:
  [1]:
    - [0]:

decode() of that raises:

File ".../toon_format/decoder.py", line 786, in decode_list_array
    raise ToonDecodeError(f"Expected {expected_length} items, but got {len(result)}")
toon_format.decoder.ToonDecodeError: Expected 1 items, but got 0

Decode: decoding the reference (TypeScript) encoding of [[[]]] returns an object instead of an array — the internal list-array markers leak out as keys:

[{"[1]": {}, "-": []}]

The decoder facet is not specific to empty arrays; any nesting depth >= 3 reproduces it (e.g. the reference encoding of [[{"s":"a:b"}]] decodes to [{"[1]": {}, "- s": "a:b"}]).

Boundary (reduced with a differential fuzzer + delta-shrinker):
[] — fine
[[]] — fine
[[[]]] — breaks (minimal reproducer)

Environment:
toon-python (toon_format): 0.9.0b1
reference impl @toon-format/toon: v2.3.0 (round-trips this input correctly)
Python: 3.14

Reproduction Steps

  1. from toon_format import encode, decode
  2. Call encode([[[]]]) — produces:
    [1]:
    [1]:
    - [0]:
  3. Call decode() on that output — raises ToonDecodeError: Expected 1 items, but got 0
    (toon-python cannot decode its own encoding)

Separately, decoding the reference @toon-format/toon encoding of [[[]]] returns
[{"[1]": {}, "-": []}] — a nested array decoded as an object.

Expected Behavior

[[[]]] round-trips unchanged: decode(encode([[[]]])) == [[[]]].
(The reference impl @toon-format/toon does this correctly.)

Actual Behavior

encode([[[]]]) produces:

[1]:
  [1]:
    - [0]:

The outer header says [1]: (one item) but the innermost level is [0]: (zero items).
decode() of that raises:

File ".../toon_format/decoder.py", line 786, in decode_list_array
    raise ToonDecodeError(f"Expected {expected_length} items, but got {len(result)}")
toon_format.decoder.ToonDecodeError: Expected 1 items, but got 0

Separately, decoding the reference @toon-format/toon encoding of [[[]]] returns an
object instead of an array:

[{"[1]": {}, "-": []}]

Environment

toon-python (toon_format): 0.9.0b1
reference impl @toon-format/toon: v2.3.0 (round-trips this input correctly)
Python: 3.14

Additional Context

The decoder facet is not specific to empty arrays; any nesting depth >= 3 reproduces it
(e.g. the reference encoding of [[{"s":"a:b"}]] decodes to [{"[1]": {}, "- s": "a:b"}]).

Boundary, reduced with a differential fuzzer + delta-shrinker:
[] — fine
[[]] — fine
[[[]]] — breaks (minimal reproducer)

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions