Rohaq Rohaq - 6 months ago 32
JSON Question

Can I implement custom indentation for pretty-printing in Python’s JSON module?

So I'm using Python 2.7, using the json module to encode the following data structure.

'layer1': {
'layer2': {
'layer3_1': [ long_list_of_stuff ],
'layer3_2': 'string'
}
}


My problem is that I'm printing everything out using pretty printing, as follows:

json.dumps(data_structure, indent=2)


Which is great, except I want to indent it all, except for the content in layer3_1 - It's a massive dictionary listing coordinates, and as such, having a single value set on each one makes pretty printing create a file with thousands of lines, with an example as follows:

{
"layer1": {
"layer2": {
"layer3_1": [
{
"x": 1,
"y": 7
},
{
"x": 0,
"y": 4
},
{
"x": 5,
"y": 3
},
{
"x": 6,
"y": 9
}
],
"layer3_2": "string"
}
}
}


What I really want is something similar to the following:

{
"layer1": {
"layer2": {
"layer3_1": [{"x":1,"y":7},{"x":0,"y":4},{"x":5,"y":3},{"x":6,"y":9}],
"layer3_2": "string"
}
}
}


I hear it's possible to extend the json module: Is it possible to set it to only turn off indenting when inside the layer3_1 object? If so, could somebody please be so kind as to help me out?

Answer

FWIW, after much ado, I was able to more or less get J.F.Sebastian's original idea to work. Here's the result after I enhanced it to print the keys of each coordinate dict in sorted order as per one of the OP's comments:

import json

class NoIndent(object):
    def __init__(self, value):
        self.value = value
    def __repr__(self):
        if not isinstance(self.value, (list, tuple)):
            return repr(self.value)
        else:  # assume it's a list or tuple of coordinates stored as dicts
            delimiters = '[]' if isinstance(self.value, list) else '()'
            pairs = ('{!r}:{}'.format(*component)
                         for coordinate in self.value
                             for component in sorted(coordinate.items()))
            pairs = ('{{{}, {}}}'.format(*pair)
                         for pair in zip(*[iter(pairs)]*2))
            return delimiters[0] + ', '.join(pairs) + delimiters[1]

class MyEncoder(json.JSONEncoder):
    def default(self, obj):
        return(repr(obj) if isinstance(obj, NoIndent) else
               json.JSONEncoder.default(self, obj))

data_structure = {
    'layer1': {
        'layer2': {
            'layer3_1': NoIndent([{"x":1,"y":7}, {"x":0,"y":4},
                                  {"x":5,"y":3}, {"x":6,"y":9}]),
            'layer3_2': 'string'
        }
    }
}

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

Output:

{
  "layer1": {
    "layer2": {
      "layer3_2": "string", 
      "layer3_1": "[{'x':1, 'y':7}, {'x':0, 'y':4}, {'x':5, 'y':3}, {'x':6, 'y':9}]"
    }
  }
}