How to Implement Custom Indentation When Pretty-Printing with the JSON Module

How to implement custom indentation when pretty-printing with the JSON module?

(Note:
The code in this answer only works with json.dumps() which returns a JSON formatted string, but not with json.dump() which writes directly to file-like objects. There's a modified version of it that works with both in my answer to the question Write two-dimensional list to JSON file.)

Updated

Below is a version of my original answer that has been revised several times. Unlike the original, which I posted only to show how to get the first idea in J.F.Sebastian's answer to work, and which like his, returned a non-indented string representation of the object. The latest updated version returns the Python object JSON formatted in isolation.

The keys of each coordinate dict will appear in sorted order, as per one of the OP's comments, but only if a sort_keys=True keyword argument is specified in the initial json.dumps() call driving the process, and it no longer changes the object's type to a string along the way. In other words, the actual type of the "wrapped" object is now maintained.

I think not understanding the original intent of my post resulted in number of folks downvoting it—so, primarily for that reason, I have "fixed" and improved my answer several times. The current version is a hybrid of my original answer coupled with some of the ideas @Erik Allik used in his answer, plus useful feedback from other users shown in the comments below this answer.

The following code appears to work unchanged in both Python 2.7.16 and 3.7.4.

from _ctypes import PyObj_FromPtr
import json
import re

class NoIndent(object):
""" Value wrapper. """
def __init__(self, value):
self.value = value

class MyEncoder(json.JSONEncoder):
FORMAT_SPEC = '@@{}@@'
regex = re.compile(FORMAT_SPEC.format(r'(\d+)'))

def __init__(self, **kwargs):
# Save copy of any keyword argument values needed for use here.
self.__sort_keys = kwargs.get('sort_keys', None)
super(MyEncoder, self).__init__(**kwargs)

def default(self, obj):
return (self.FORMAT_SPEC.format(id(obj)) if isinstance(obj, NoIndent)
else super(MyEncoder, self).default(obj))

def encode(self, obj):
format_spec = self.FORMAT_SPEC # Local var to expedite access.
json_repr = super(MyEncoder, self).encode(obj) # Default JSON.

# Replace any marked-up object ids in the JSON repr with the
# value returned from the json.dumps() of the corresponding
# wrapped Python object.
for match in self.regex.finditer(json_repr):
# see https://stackoverflow.com/a/15012814/355230
id = int(match.group(1))
no_indent = PyObj_FromPtr(id)
json_obj_repr = json.dumps(no_indent.value, sort_keys=self.__sort_keys)

# Replace the matched id string with json formatted representation
# of the corresponding Python object.
json_repr = json_repr.replace(
'"{}"'.format(format_spec.format(id)), json_obj_repr)

return json_repr

if __name__ == '__main__':
from string import ascii_lowercase as letters

data_structure = {
'layer1': {
'layer2': {
'layer3_1': NoIndent([{"x":1,"y":7}, {"x":0,"y":4}, {"x":5,"y":3},
{"x":6,"y":9},
{k: v for v, k in enumerate(letters)}]),
'layer3_2': 'string',
'layer3_3': NoIndent([{"x":2,"y":8,"z":3}, {"x":1,"y":5,"z":4},
{"x":6,"y":9,"z":8}]),
'layer3_4': NoIndent(list(range(20))),
}
}
}

print(json.dumps(data_structure, cls=MyEncoder, sort_keys=True, indent=2))

Output:

{
"layer1": {
"layer2": {
"layer3_1": [{"x": 1, "y": 7}, {"x": 0, "y": 4}, {"x": 5, "y": 3}, {"x": 6, "y": 9}, {"a": 0, "b": 1, "c": 2, "d": 3, "e": 4, "f": 5, "g": 6, "h": 7, "i": 8, "j": 9, "k": 10, "l": 11, "m": 12, "n": 13, "o": 14, "p": 15, "q": 16, "r": 17, "s": 18, "t": 19, "u": 20, "v": 21, "w": 22, "x": 23, "y": 24, "z": 25}],
"layer3_2": "string",
"layer3_3": [{"x": 2, "y": 8, "z": 3}, {"x": 1, "y": 5, "z": 4}, {"x": 6, "y": 9, "z": 8}],
"layer3_4": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
}
}
}

How to custom indent json dump?

As stated in the comments, it doesn't make functional difference and you will need custom pretty-print. something like

import json
import textwrap

spam = {"a": 1, "b": "2",
"list": [{"c": 3, "d": 4,}]}

eggs = json.dumps(spam, indent=2).splitlines()
eggs = '\n'.join([eggs[0], textwrap.dedent('\n'.join(eggs[1:-1])), eggs[-1]])
print(eggs)
with open('spam.json', 'w') as f:
f.write(eggs)

output

{
"a": 1,
"b": "2",
"list": [
{
"c": 3,
"d": 4
}
]
}

How to prettyprint a JSON file?

Use the indent= parameter of json.dump() or json.dumps() to specify how many spaces to indent by:

>>> import json
>>>
>>> your_json = '["foo", {"bar": ["baz", null, 1.0, 2]}]'
>>> parsed = json.loads(your_json)
>>> print(json.dumps(parsed, indent=4))
[
"foo",
{
"bar": [
"baz",
null,
1.0,
2
]
}
]

To parse a file, use json.load():

with open('filename.txt', 'r') as handle:
parsed = json.load(handle)

How to pretty print a json.loads method in Python

I think there might be some confusion on what exactly is going on with the json.dumps() and json.loads() methods.

# Serializing boy object to a str named boyJSON 
# -> resulting str will have JSON format
boyJSON = json.dumps(boy, default=encode_kid, indent=2)

# Serializing boyJSON str to another str boyPYTHON
# -> resulting str will have JSON format, now with str literals
boyPYTHON = json.dumps(boyJSON, indent=2)

That is why boyJSON and boyPYTHON will print very differently. If you want them to print the exact same, i.e. "pretty", try this:

boyJSON = json.dumps(boy, default=encode_kid, indent=2)
boyPYTHON = json.loads(boyJSON) # Converts boyJSON str to a dict

print(boyJSON)
print(json.dumps(boyPYTHON, indent=2)) # Print boyPYTHON dict converted to a str with JSON format

Output:

>>> print(boyJSON)
{
"name": "Jimmy",
"age": 10,
"nickname": null,
"Is friendly": false
}

>>> print(json.dumps(boyPYTHON, indent=2))
{
"name": "Jimmy",
"age": 10,
"nickname": null,
"Is friendly": false
}

>>> print(boyPYTHON)
{'name': 'Jimmy', 'age': 10, 'nickname': None, 'Is friendly': False}

>>> from pprint import pprint
>>> pprint(boyPYTHON, width=1)
{'Is friendly': False,
'age': 10,
'name': 'Jimmy',
'nickname': None}

Dumping a JSON using tab indents (not spaces)

Python 2.7

there is a workaround that can be implemented using regular expressions :

import re
dump = json.dumps(data, sort_keys=True, indent=4, separators=(',', ': '))
#Replaces spaces with tab
new_data = re.sub('\n +', lambda match: '\n' + '\t' * (len(match.group().strip('\n')) / 3), dump)
json.dump(new_data, open('dev_integrated.json', 'w')

Python 3.2+

From the Docs :

If indent is a non-negative integer or string, then JSON array elements and object members will be pretty-printed with that indent level. An indent level of 0, negative, or "" will only insert newlines. None (the default) selects the most compact representation. Using a positive integer indent indents that many spaces per level. If indent is a string (such as "\t"), that string is used to indent each level.

Hence the TAB-indentation can be implemented as follows:

json.dump(jString, open('dev_integrated.json', 'w'), sort_keys=True, indent='\t', separators=(',', ': '))

Pretty-Print JSON Data to a File using Python

You should use the optional argument indent.

header, output = client.request(twitterRequest, method="GET", body=None,
headers=None, force_auth_header=True)

# now write output to a file
twitterDataFile = open("twitterData.json", "w")
# magic happens here to make it pretty-printed
twitterDataFile.write(simplejson.dumps(simplejson.loads(output), indent=4, sort_keys=True))
twitterDataFile.close()

How to pretty print nested dictionaries?

I'm not sure how exactly you want the formatting to look like, but you could start with a function like this:

def pretty(d, indent=0):
for key, value in d.items():
print('\t' * indent + str(key))
if isinstance(value, dict):
pretty(value, indent+1)
else:
print('\t' * (indent+1) + str(value))


Related Topics



Leave a reply



Submit