36
votes

I use this code to pretty print a dict into JSON:

import json
d = {'a': 'blah', 'b': 'foo', 'c': [1,2,3]}
print json.dumps(d, indent = 2, separators=(',', ': '))

Output:

{
  "a": "blah",
  "c": [
    1,
    2,
    3
  ],
  "b": "foo"
}

This is a little bit too much (newline for each list element!).

Which syntax should I use to have this:

{
  "a": "blah",
  "c": [1, 2, 3],
  "b": "foo"
}

instead?

7
This isn't exactly an answer to your question, but if you're just looking for a data format that's easy on the eyes, you might try YAML. There are two syntaxes for collections like lists and dictionaries, "block" (where indentation shows the nesting) and "flow" (where brackets do that job). By default the output from PyYAML uses "flow" syntax for lists and dictionaries that don't contain any other containers, which sounds like exactly what you want for your pretty printing. - Blckknght
Thanks @Blckknght. Is YAML mature/stable/portable and likely to be available in the future years/future versions of python? Is it going to be a standard? (sorry for all these questions ;) ) - Basj
Another remark: I wanted to avoid conversion into string, because when I load back my JSON file into a dict, if it is a string, I don't have access anymore to the list (or I would need to parse the string into list but that's a shame to have to do this ...) - Basj
There's gotta be a way to do this by subclassing json.JSONEncoder, but I haven't figured it out. - ford
@Basj: I'm not really a YAML expert, but my understanding is that it's pretty stable and mature, though not nearly as widely used as JSON. You can find its standards at the official YAML website, though the Wikipedia page seems quite a bit better at giving an overview. JSON is a subset of the latest version of YAML (and the incompatibilities with earlier YAML versions were apparently rarely encountered). - Blckknght

7 Answers

20
votes

I ended up using jsbeautifier:

import jsbeautifier
opts = jsbeautifier.default_options()
opts.indent_size = 2
jsbeautifier.beautify(json.dumps(d), opts)

Output:

{
  "a": "blah",
  "c": [1, 2, 3],
  "b": "foo"
}
8
votes

Write your own JSON serializer:

import numpy

INDENT = 3
SPACE = " "
NEWLINE = "\n"

def to_json(o, level=0):
    ret = ""
    if isinstance(o, dict):
        ret += "{" + NEWLINE
        comma = ""
        for k,v in o.iteritems():
            ret += comma
            comma = ",\n"
            ret += SPACE * INDENT * (level+1)
            ret += '"' + str(k) + '":' + SPACE
            ret += to_json(v, level + 1)

        ret += NEWLINE + SPACE * INDENT * level + "}"
    elif isinstance(o, basestring):
        ret += '"' + o + '"'
    elif isinstance(o, list):
        ret += "[" + ",".join([to_json(e, level+1) for e in o]) + "]"
    elif isinstance(o, bool):
        ret += "true" if o else "false"
    elif isinstance(o, int):
        ret += str(o)
    elif isinstance(o, float):
        ret += '%.7g' % o
    elif isinstance(o, numpy.ndarray) and numpy.issubdtype(o.dtype, numpy.integer):
        ret += "[" + ','.join(map(str, o.flatten().tolist())) + "]"
    elif isinstance(o, numpy.ndarray) and numpy.issubdtype(o.dtype, numpy.inexact):
        ret += "[" + ','.join(map(lambda x: '%.7g' % x, o.flatten().tolist())) + "]"
    else:
        raise TypeError("Unknown type '%s' for json serialization" % str(type(o)))
    return ret

inputJson = {'a': 'blah', 'b': 'foo', 'c': [1,2,3]}
print to_json(inputJson)

Output:

{
   "a": "blah",
   "c": [1,2,3],
   "b": "foo"
}
5
votes

Another alternative is print(json.dumps(d, indent=None, separators=(',\n', ': ')))

The output will be:

{"a": "blah",
"c": [1,
2,
3],
"b": "foo"}

Note that though the official docs at https://docs.python.org/2.7/library/json.html#basic-usage say the default args are separators=None --that actually means "use default of separators=(', ',': ') ). Note also that the comma separator doesn't distinguish between k/v pairs and list elements.

4
votes

After years, I found a solution with the built-in pprint module:

import pprint
d = {'a': 'blah', 'b': 'foo', 'c': [1,2,3]}
pprint.pprint(d)                    # default width=80 so this will be printed in a single line
pprint.pprint(d, width=20)          # here it will be wrapped exactly as expected

Output:

{'a': 'blah',  
 'b': 'foo',  
 'c': [1, 2, 3]}

0
votes

I couldn't get jsbeautifier to do much, so I used regular expressions. Had json pattern like

'{\n    "string": [\n        4,\n        1.0,\n        6,\n        1.0,\n        8,\n        1.0,\n        9,\n        1.0\n    ],\n...'

that I wanted as

'{\n    "string": [ 4, 1.0, 6, 1.0, 8, 1.0, 9, 1.0],\n'

so

t = json.dumps(apriori, indent=4)
t = re.sub('\[\n {7}', '[', t)
t = re.sub('(?<!\]),\n {7}', ',', t)
t = re.sub('\n {4}\]', ']', t)
outfile.write(t)

So instead of one "dump(apriori, t, indent=4)", I had those 5 lines.

-1
votes

This has been bugging me for a while as well, I found a 1 liner I'm almost happy with:

print json.dumps(eval(str(d).replace('[', '"[').replace(']', ']"').replace('(', '"(').replace(')', ')"')), indent=2).replace('\"\\"[', '[').replace(']\\"\"', ']').replace('\"\\"(', '(').replace(')\\"\"', ')')

That essentially convert all lists or tuples to a string, then uses json.dumps with indent to format the dict. Then you just need to remove the quotes and your done!

Note: I convert the dict to string to easily convert all lists/tuples no matter how nested the dict is.

PS. I hope the Python Police won't come after me for using eval... (use with care)

-2
votes

Perhaps not quite as efficient, but consider a simpler case (somewhat tested in Python 3, but probably would work in Python 2 also):

def dictJSONdumps( obj, levels, indentlevels = 0 ):
    import json
    if isinstance( obj, dict ):
        res = []
        for ix in sorted( obj, key=lambda x: str( x )):
            temp = ' ' * indentlevels + json.dumps( ix, ensure_ascii=False ) + ': '
            if levels:
                temp += dictJSONdumps( obj[ ix ], levels-1, indentlevels+1 )
            else:
                temp += json.dumps( obj[ ix ], ensure_ascii=False )
            res.append( temp )
        return '{\n' + ',\n'.join( res ) + '\n}'
    else:
        return json.dumps( obj, ensure_ascii=False )

This might give you some ideas, short of writing your own serializer completely. I used my own favorite indent technique, and hard-coded ensure_ascii, but you could add parameters and pass them along, or hard-code your own, etc.