Skip to content

Stop joined JSON containers from indenting nested sequences - #169

Merged
ESultanik merged 1 commit into
masterfrom
158-joined-indent
Sep 9, 2026
Merged

ESultanik merged 1 commit into
masterfrom
158-joined-indent

Conversation

@ESultanik

Copy link
Copy Markdown
Collaborator

Closes #158

What was wrong

SequenceFormatter.print_SequenceNode always wraps its items in self.items_indent(printer), and the default implementation always calls printer.indent(). Printer.write materializes indentation lazily, on the first write after a newline, so a container that never emits a newline still raises the indent counter for everything nested inside it.

--join-dict-items suppresses the newlines between key/value pairs, so the dict's extra level of indentation was invisible at the dict level but still counted. A nested list added its own level and did emit newlines, so its items came out eight spaces deep and its closing bracket four. --join-lists had the same latent problem for anything nested inside a joined list.

JSONListFormatter and JSONDictFormatter now return the printer unchanged from items_indent when their join option is set, so indentation tracks the depth that actually breaks lines. Printer is itself a context manager, and pydiff, yaml, ini, and csv already return one this way.

Before and after

All examples are {"foo": [1, 2, 3], "bar": "baz"} diffed against itself.

graphtage -jd a.json a.json:

{"bar": "baz","foo": [
        1,
        2,
        3
    ]}
{"bar": "baz", "foo": [
    1,
    2,
    3
]}

graphtage -jl a.json a.json was {\n "bar": "baz",\n "foo": [1,2,3]\n} and is now:

{
    "bar": "baz",
    "foo": [1, 2, 3]
}

graphtage -j a.json a.json was {"bar": "baz","foo": [1,2,3]} and is now:

{"bar": "baz", "foo": [1, 2, 3]}

Nesting now composes. With -jd on {"x": [{"y": [1, 2]}, 3]}, only the two lists break lines, so only they indent:

{"x": [
    {"y": [
        1,
        2
    ]},
    3
]}

The delimiter space

Both options now emit a single space in place of the newline they suppress, so joined output reads [1, 2, 3] and {"bar": "baz", "foo": …}. The space is applied in item_newline, which runs only between items, so it never leaves trailing whitespace before a newline in the unjoined case, and it does not appear before the first item or after the last.

Applying it to both options keeps --condensed self-consistent, and it matches the spacing the same documents already get when pretty-printed, where "bar": "baz" and the list items are separated by a comma and a line break rather than a bare comma.

Validation

  • New test/test_json.py covers -jl, -jd, and both together, including the nested-list case, the deeper {"x": [{"y": [1, 2]}, 3]} case, the delimiter spacing, and a joined diff containing an insertion and a removal. There was no test for either option before.
  • Against the unfixed formatter, 7 of the 8 new tests fail; the eighth is the default pretty-printed baseline, which this change must not alter. All 8 pass with the fix.
  • ruff check graphtage test docs bindist: clean.
  • pytest: 148 passed, including the test/test_formatting.py fuzz round-trip suite (1000 JSON iterations).
  • make -C docs html SPHINXOPTS="-W --keep-going": build succeeded.
  • uv lock --check fails in my environment because of a global exclude-newer setting; it fails identically on an unmodified tree, and no dependency metadata is touched here.
  • Checked that --html -jd emits a single indentation div for the nested list, and that pydiff output is byte-identical without the join options.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GypKU5KdLfs2Cf8kS2TzJa

`SequenceFormatter.print_SequenceNode` always wraps its items in
`self.items_indent(printer)`, and the default implementation always calls
`printer.indent()`. `Printer.write` materializes indentation lazily, on the
first write after a newline, so a container that never emits a newline still
raises the indent counter for everything nested inside it.

`--join-dict-items` suppresses the newlines between key/value pairs, so the
dict's extra level of indentation was invisible at the dict level but still
counted. A list nested inside that dict added its own level and did emit
newlines, so its items came out eight spaces deep and its closing bracket
four, as though the collapsed dict were still spread over several lines.
`--join-lists` had the same latent problem for anything nested inside a
joined list.

Both JSON sub-formatters now return the printer unchanged from
`items_indent` when their join option is set, so indentation tracks the depth
that actually breaks lines. `Printer` is itself a context manager, and
several other formatters already return one this way.

Joined items were also run together with a bare comma, giving `[1,2,3]` and
`{"bar": "baz","foo": …}`. Both formatters now emit a single space in place
of the newline they suppress, matching the spacing that the same documents
get when they are pretty-printed.

The README recorded the old, wrong `-jd` and `-j` output; those blocks are
regenerated from the real command.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GypKU5KdLfs2Cf8kS2TzJa
@ESultanik
ESultanik merged commit 2ffc3a8 into master Sep 9, 2026
12 checks passed
@ESultanik
ESultanik deleted the 158-joined-indent branch September 9, 2026 14:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

--join-dict-items indents nested lists as though they were not collapsed

1 participant