Skip to content

Instantly share code, notes, and snippets.

@spezold
Created August 9, 2024 14:52
Show Gist options
  • Save spezold/0dbf5454200af85b2f04cd9986ba4fdb to your computer and use it in GitHub Desktop.
Save spezold/0dbf5454200af85b2f04cd9986ba4fdb to your computer and use it in GitHub Desktop.
Dump given JSON document to a string, flattening lists (recursively) but respecting the indent for other objects.
import json
def json_dumps_compact(data, indent: int | str, **kwargs) -> str:
"""
Dump given JSON document to a string, flattening lists (recursively) but respecting the indent for other objects.
:param data: JSON object to be dumped
:param indent: indent level for JSON object members (None is not supported)
:param kwargs: other arguments passed to :func:`json.dumps` ("separators" is not supported)
:return: resulting string
"""
if indent is None:
raise ValueError("Unsupported argument 'indent=None' given")
if kwargs.get("separators") is not None:
raise ValueError("Unsupported argument 'separators' given")
# The "not" cases are necessary because empty lists/dicts by default end up with both brackets on the same line
is_list_start = lambda line_: line_.endswith("[")
is_list_end = lambda line_: line_.endswith(("],", "]")) and not line_.endswith(("[],", "[]"))
is_dict_start = lambda line_: line_.endswith("{")
is_dict_end = lambda line_: line_.endswith(("},", "}")) and not line_.endswith(("{},", "{}"))
at_bound = lambda lo, up: is_list_start(lo) or is_dict_start(lo) or is_list_end(up) or is_dict_end(up)
lines = json.dumps(data, indent=indent, **kwargs).split("\n")
processed_lines = []
current_line = []
lists_to_close = 0
for line in lines:
# If a list starts, lift all following lines onto the same line until it is closed;
# strip indents for all except first start of a list
if is_list_start(line):
current_line.append(line if not lists_to_close else line.strip())
lists_to_close += 1
elif is_list_end(line):
lists_to_close -= 1
current_line.append(line.strip())
if not lists_to_close:
# Final list is closed: assemble line; provide separators only between actual items
seps = ("" if at_bound(lo, up) else " " for lo, up in zip(current_line[:-1], current_line[1:]))
processed_lines.append("".join(i for t in zip(current_line, seps) for i in t) + current_line[-1])
current_line = []
else:
if lists_to_close:
current_line.append(line.strip()) # Inside list → append to current line
else:
processed_lines.append(line) # Append to processed lines
return "\n".join(processed_lines)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment