-
Notifications
You must be signed in to change notification settings - Fork 0
/
prettyjson.py
146 lines (116 loc) · 5.92 KB
/
prettyjson.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
import types
# https://stackoverflow.com/a/56497521/104668
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, level=0)
return indentitems(items, indent, level=0)
def getsubitems(obj, itemkey, islast, maxlinelength, level):
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)
# 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: opening, closing, keys = ("[", "]", range(0, len(obj)))
elif istuple: opening, closing, keys = ("[", "]", range(0, len(obj))) # tuples are converted into json arrays
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_ = ""
if isdict: itemkey_ = basictype2str(k)
inner, is_inner_inline = getsubitems(obj[k], itemkey_, islast_, maxlinelength, level+1)
subitems.extend(inner) # inner can be a string or a list
is_inline = is_inline and is_inner_inline # if a child couldn't be rendered inline, then we are not able either
# 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 = ""
for item in subitems: str += item + " " # insert space between items, comma is already there
subitems = [ str.strip() ] # wrap concatenated content in a new list
else:
is_inline = False
# attempt to render the outer brackets + inner tokens in one line
if is_inline:
item_text = ""
if len(subitems) > 0: item_text = subitems[0]
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
items.append(subitems) # Append children to parent list as a nested list
items.append(closing) # closing brackets
return items, is_inline
def basictype2str(obj):
if isinstance (obj, str):
strobj = "\"" + str(obj) + "\""
elif isinstance(obj, bool):
strobj = { True: "true", False: "false" }[obj]
else:
strobj = str(obj)
return strobj
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
if level==0 and islast:
res += indentstr + item
else:
res += indentstr + item + "\n"
return res