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

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions