Skip to content

Instantly share code, notes, and snippets.

@muhammedfurkan
Created May 6, 2022 10:43
Show Gist options
  • Save muhammedfurkan/9296b927cd64613b5cf5b404030fbdd6 to your computer and use it in GitHub Desktop.
Save muhammedfurkan/9296b927cd64613b5cf5b404030fbdd6 to your computer and use it in GitHub Desktop.
def prettyjson(obj, indent=2, maxlinelength=80):
"""Renders JSON content with indentation and line splits/concatenations to fit maxlinelength.
Only dicts, lists and basic types are supported"""
items, _ = getsubitems(obj, itemkey="", islast=True,
maxlinelength=maxlinelength - indent, indent=indent)
return indentitems(items, indent, level=0)
def getsubitems(obj, itemkey, islast, maxlinelength, indent):
items = []
is_inline = True # at first, assume we can concatenate the inner tokens into one line
isdict = isinstance(obj, dict)
islist = isinstance(obj, list)
istuple = isinstance(obj, tuple)
isbasictype = not (isdict or islist or istuple)
maxlinelength = max(0, maxlinelength)
# build json content as a list of strings or child lists
if isbasictype:
# render basic type
keyseparator = "" if itemkey == "" else ": "
itemseparator = "" if islast else ","
items.append(itemkey + keyseparator +
basictype2str(obj) + itemseparator)
else:
# render lists/dicts/tuples
if isdict:
opening, closing, keys = ("{", "}", iter(obj.keys()))
elif islist or istuple:
opening, closing, keys = "[", "]", range(len(obj))
if itemkey != "":
opening = itemkey + ": " + opening
if not islast:
closing += ","
count = 0
itemkey = ""
subitems = []
# get the list of inner tokens
for (i, k) in enumerate(keys):
islast_ = i == len(obj)-1
itemkey_ = basictype2str(k) if isdict else ""
inner, is_inner_inline = getsubitems(
obj[k], itemkey_, islast_, maxlinelength - indent, indent)
# inner can be a string or a list
subitems.extend(inner)
# if a child couldn't be rendered inline, then we are not able either
is_inline = is_inline and is_inner_inline
# fit inner tokens into one or multiple lines, each no longer than maxlinelength
if is_inline:
multiline = True
# in Multi-line mode items of a list/dict/tuple can be rendered in multiple lines if they don't fit on one.
# suitable for large lists holding data that's not manually editable.
# in Single-line mode items are rendered inline if all fit in one line, otherwise each is rendered in a separate line.
# suitable for smaller lists or dicts where manual editing of individual items is preferred.
# this logic may need to be customized based on visualization requirements:
if (isdict):
multiline = False
if (islist):
multiline = True
if (multiline):
lines = []
current_line = ""
current_index = 0
for (i, item) in enumerate(subitems):
item_text = item
if i < len(inner)-1:
item_text = item + ","
if len(current_line) > 0:
try_inline = current_line + " " + item_text
else:
try_inline = item_text
if (len(try_inline) > maxlinelength):
# push the current line to the list if maxlinelength is reached
if len(current_line) > 0:
lines.append(current_line)
current_line = item_text
else:
# keep fitting all to one line if still below maxlinelength
current_line = try_inline
# Push the remainder of the content if end of list is reached
if (i == len(subitems)-1):
lines.append(current_line)
subitems = lines
if len(subitems) > 1:
is_inline = False
else: # single-line mode
totallength = len(subitems)-1 # spaces between items
for item in subitems:
totallength += len(item)
if (totallength <= maxlinelength):
str = "".join(item + " " for item in subitems)
# wrap concatenated content in a new list
subitems = [str.strip()]
else:
is_inline = False
# attempt to render the outer brackets + inner tokens in one line
if is_inline:
item_text = subitems[0] if subitems else ""
if len(opening) + len(item_text) + len(closing) <= maxlinelength:
items.append(opening + item_text + closing)
else:
is_inline = False
# if inner tokens are rendered in multiple lines already, then the outer brackets remain in separate lines
if not is_inline:
items.append(opening) # opening brackets
# Append children to parent list as a nested list
items.append(subitems)
items.append(closing) # closing brackets
return items, is_inline
def basictype2str(obj):
if isinstance(obj, str):
return "\"" + str(obj) + "\""
if isinstance(obj, bool):
return {True: "true", False: "false"}[obj]
return str(obj)
def indentitems(items, indent, level):
"""Recursively traverses the list of json lines, adds indentation based on the current depth"""
res = ""
indentstr = " " * (indent * level)
for (i, item) in enumerate(items):
if isinstance(item, list):
res += indentitems(item, indent, level+1)
else:
islast = (i == len(items)-1)
# no new line character after the last rendered line
res += indentstr + item if level == 0 and islast else indentstr + item + "\n"
return res
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment