Stop joined JSON containers from indenting nested sequences - #169
Merged
Merged
Conversation
`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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #158
What was wrong
SequenceFormatter.print_SequenceNodealways wraps its items inself.items_indent(printer), and the default implementation always callsprinter.indent().Printer.writematerializes 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-itemssuppresses 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-listshad the same latent problem for anything nested inside a joined list.JSONListFormatterandJSONDictFormatternow return the printer unchanged fromitems_indentwhen their join option is set, so indentation tracks the depth that actually breaks lines.Printeris itself a context manager, andpydiff,yaml,ini, andcsvalready 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.jsonwas{\n "bar": "baz",\n "foo": [1,2,3]\n}and is now:{ "bar": "baz", "foo": [1, 2, 3] }graphtage -j a.json a.jsonwas{"bar": "baz","foo": [1,2,3]}and is now:{"bar": "baz", "foo": [1, 2, 3]}Nesting now composes. With
-jdon{"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 initem_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
--condensedself-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
test/test_json.pycovers-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.ruff check graphtage test docs bindist: clean.pytest: 148 passed, including thetest/test_formatting.pyfuzz round-trip suite (1000 JSON iterations).make -C docs html SPHINXOPTS="-W --keep-going": build succeeded.uv lock --checkfails in my environment because of a globalexclude-newersetting; it fails identically on an unmodified tree, and no dependency metadata is touched here.--html -jdemits a single indentationdivfor the nested list, and thatpydiffoutput is byte-identical without the join options.🤖 Generated with Claude Code
https://claude.ai/code/session_01GypKU5KdLfs2Cf8kS2TzJa