¿Cómo implementar la sangría personalizada cuando se imprime bastante con el módulo JSON?

Entonces estoy usando Python 2.7, usando el módulo json para codificar la siguiente estructura de datos:

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

Mi problema es que estoy imprimiendo todo utilizando una impresión bonita, de la siguiente manera:

 json.dumps(data_structure, indent=2) 

Lo que es genial, excepto que quiero sangrarlo todo, excepto por el contenido en "layer3_1" : es un diccionario masivo que enumera las coordenadas, y como tal, tener un solo valor establecido en cada una hace una impresión muy bonita, crea un archivo con miles de líneas , con un ejemplo como sigue:

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

Lo que realmente quiero es algo similar a lo siguiente:

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

Escuché que es posible extender el módulo json : ¿Es posible configurarlo para que solo desactive la sangría cuando está dentro del objeto "layer3_1" ? Si es así, ¿podría alguien decirme cómo?

Actualizado

A continuación hay una versión de mi respuesta original que ha sido revisada varias veces. A diferencia del original, que publiqué solo para mostrar cómo obtener que funcionara la primera idea de la respuesta de JFSebastian, y que, como la suya, devolvió una representación de cadena sin sangría del objeto. La última versión actualizada devuelve el objeto Python JSON formateado de forma aislada.

Las claves de cada orden de coordenadas aparecerán ordenadas, según uno de los comentarios del OP, pero solo si un sort_keys=True keyword se especifica en la llamada json.dumps() inicial que impulsa el proceso y ya no cambia el tipo de objeto a una cadena en el camino. En otras palabras, el tipo real del objeto “envuelto” ahora se mantiene.

Creo que no entender la intención original de mi publicación resultó en un número de personas que votaron a la baja, así que, principalmente por esa razón, he “arreglado” y mejorado mi respuesta varias veces. La versión actual es un híbrido de mi respuesta original, junto con algunas de las ideas que @Erik Allik utilizó en su respuesta , además de comentarios útiles de otros usuarios que se muestran en los comentarios debajo de esta respuesta.

El siguiente código parece funcionar sin cambios tanto en Python 2.7.14 como en 3.6.5.

 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)) 

Salida:

 { "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] } } } 

A bodge, pero una vez que tenga la cadena de dumps (), puede realizar una sustitución de expresiones regulares en ella, si está seguro del formato de su contenido. Algo a lo largo de las líneas de:

 s = json.dumps(data_structure, indent=2) s = re.sub('\s*{\s*"(.)": (\d+),\s*"(.)": (\d+)\s*}(,?)\s*', r'{"\1":\2,"\3":\4}\5', s) 

La siguiente solución parece funcionar correctamente en Python 2.7.x. Utiliza una solución alternativa tomada del codificador JSON personalizado en Python 2.7 para insertar código JavaScript simple para evitar que los objetos con encoding personalizada terminen como cadenas JSON en la salida utilizando un esquema de reemplazo basado en UUID.

 class NoIndent(object): def __init__(self, value): self.value = value class NoIndentEncoder(json.JSONEncoder): def __init__(self, *args, **kwargs): super(NoIndentEncoder, self).__init__(*args, **kwargs) self.kwargs = dict(kwargs) del self.kwargs['indent'] self._replacement_map = {} def default(self, o): if isinstance(o, NoIndent): key = uuid.uuid4().hex self._replacement_map[key] = json.dumps(o.value, **self.kwargs) return "@@%s@@" % (key,) else: return super(NoIndentEncoder, self).default(o) def encode(self, o): result = super(NoIndentEncoder, self).encode(o) for k, v in self._replacement_map.iteritems(): result = result.replace('"@@%s@@"' % (k,), v) return result 

Luego esto

 obj = { "layer1": { "layer2": { "layer3_2": "string", "layer3_1": NoIndent([{"y": 7, "x": 1}, {"y": 4, "x": 0}, {"y": 3, "x": 5}, {"y": 9, "x": 6}]) } } } print json.dumps(obj, indent=2, cls=NoIndentEncoder) 

produce la siguiente salida:

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

También pasa correctamente todas las opciones (excepto la indent ), por ejemplo, sort_keys=True hasta la llamada json.dumps anidada.

 obj = { "layer1": { "layer2": { "layer3_1": NoIndent([{"y": 7, "x": 1, }, {"y": 4, "x": 0}, {"y": 3, "x": 5, }, {"y": 9, "x": 6}]), "layer3_2": "string", } } } print json.dumps(obj, indent=2, sort_keys=True, cls=NoIndentEncoder) 

Salidas correctamente:

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

También se puede combinar con, por ejemplo, collections.OrderedDict :

 obj = { "layer1": { "layer2": { "layer3_2": "string", "layer3_3": NoIndent(OrderedDict([("b", 1), ("a", 2)])) } } } print json.dumps(obj, indent=2, cls=NoIndentEncoder) 

salidas :

 { "layer1": { "layer2": { "layer3_3": {"b": 1, "a": 2}, "layer3_2": "string" } } } 

Esto produce el resultado esperado del OP:

 import json class MyJSONEncoder(json.JSONEncoder): def iterencode(self, o, _one_shot=False): list_lvl = 0 for s in super(MyJSONEncoder, self).iterencode(o, _one_shot=_one_shot): if s.startswith('['): list_lvl += 1 s = s.replace('\n', '').rstrip() elif 0 < list_lvl: s = s.replace('\n', '').rstrip() if s and s[-1] == ',': s = s[:-1] + self.item_separator elif s and s[-1] == ':': s = s[:-1] + self.key_separator if s.endswith(']'): list_lvl -= 1 yield s o = { "layer1":{ "layer2":{ "layer3_1":[{"y":7,"x":1},{"y":4,"x":0},{"y":3,"x":5},{"y":9,"x":6}], "layer3_2":"string", "layer3_3":["aaa\nbbb","ccc\nddd",{"aaa\nbbb":"ccc\nddd"}], "layer3_4":"aaa\nbbb", } } } jsonstr = json.dumps(o, indent=2, separators=(',', ':'), sort_keys=True, cls=MyJSONEncoder) print(jsonstr) o2 = json.loads(jsonstr) print('identical objects: {}'.format((o == o2))) 

Tu podrías intentar:

  • Marque las listas que no deben NoIndentList reemplazándolas con NoIndentList :

     class NoIndentList(list): pass 
  • reemplaza el método json.Encoder.default para producir una representación de cadena sin sangría para NoIndentList .

    Puede simplemente volver a la lista y llamar a json.dumps () sin indent para obtener una sola línea

Parece que el enfoque anterior no funciona para el módulo json:

 import json import sys class NoIndent(object): def __init__(self, value): self.value = value def default(o, encoder=json.JSONEncoder()): if isinstance(o, NoIndent): return json.dumps(o.value) return encoder.default(o) L = [dict(x=x, y=y) for x in range(1) for y in range(2)] obj = [NoIndent(L), L] json.dump(obj, sys.stdout, default=default, indent=4) 

Produce una salida no válida (la lista se serializa como una cadena):

 [ "[{\"y\": 0, \"x\": 0}, {\"y\": 1, \"x\": 0}]", [ { "y": 0, "x": 0 }, { "y": 1, "x": 0 } ] ] 

Si puedes usar yaml entonces el método funciona:

 import sys import yaml class NoIndentList(list): pass def noindent_list_presenter(dumper, data): return dumper.represent_sequence(u'tag:yaml.org,2002:seq', data, flow_style=True) yaml.add_representer(NoIndentList, noindent_list_presenter) obj = [ [dict(x=x, y=y) for x in range(2) for y in range(1)], [dict(x=x, y=y) for x in range(1) for y in range(2)], ] obj[0] = NoIndentList(obj[0]) yaml.dump(obj, stream=sys.stdout, indent=4) 

Produce:

 - [{x: 0, y: 0}, {x: 1, y: 0}] - - {x: 0, y: 0} - {x: 0, y: 1} 

es decir, la primera lista se serializa usando [] y todos los elementos están en una línea, la segunda lista usa una línea por elemento.

Como nota al margen, este sitio web tiene un JavaScript incorporado que evitará el avance de línea en las cadenas JSON cuando las líneas tengan menos de 70 caracteres:

http://www.csvjson.com/json_beautifier

(Se implementó utilizando una versión modificada de JSON-js )

Seleccione “Inline short arrays”

Ideal para ver rápidamente los datos que tiene en el búfer de copia.

Aquí hay una solución de posprocesamiento si tiene demasiados tipos diferentes de objetos que contribuyen a JSON para intentar el método JSONEncoder y demasiados tipos variables para usar una expresión regular. Esta función contrae los espacios en blanco después de un nivel específico, sin necesidad de conocer los detalles de los datos en sí.

 def collapse_json(text, indent=12): """Compacts a string of json data by collapsing whitespace after the specified indent level NOTE: will not produce correct results when indent level is not a multiple of the json indent level """ initial = " " * indent out = [] # final json output sublevel = [] # accumulation list for sublevel entries pending = None # holder for consecutive entries at exact indent level for line in text.splitlines(): if line.startswith(initial): if line[indent] == " ": # found a line indented further than the indent level, so add # it to the sublevel list if pending: # the first item in the sublevel will be the pending item # that was the previous line in the json sublevel.append(pending) pending = None item = line.strip() sublevel.append(item) if item.endswith(","): sublevel.append(" ") elif sublevel: # found a line at the exact indent level *and* we have sublevel # items. This means the sublevel items have come to an end sublevel.append(line.strip()) out.append("".join(sublevel)) sublevel = [] else: # found a line at the exact indent level but no items indented # further, so possibly start a new sub-level if pending: # if there is already a pending item, it means that # consecutive entries in the json had the exact same # indentation and that last pending item was not the start # of a new sublevel. out.append(pending) pending = line.rstrip() else: if pending: # it's possible that an item will be pending but not added to # the output yet, so make sure it's not forgotten. out.append(pending) pending = None if sublevel: out.append("".join(sublevel)) out.append(line) return "\n".join(out) 

Por ejemplo, usando esta estructura como entrada para json.dumps con un nivel de sangría de 4:

 text = json.dumps({"zero": ["first", {"second": 2, "third": 3, "fourth": 4, "items": [[1,2,3,4], [5,6,7,8], 9, 10, [11, [12, [13, [14, 15]]]]]}]}, indent=4) 

Aquí está la salida de la función en varios niveles de sangría:

 >>> print collapse_json(text, indent=0) {"zero": ["first", {"items": [[1, 2, 3, 4], [5, 6, 7, 8], 9, 10, [11, [12, [13, [14, 15]]]]], "second": 2, "fourth": 4, "third": 3}]} >>> print collapse_json(text, indent=4) { "zero": ["first", {"items": [[1, 2, 3, 4], [5, 6, 7, 8], 9, 10, [11, [12, [13, [14, 15]]]]], "second": 2, "fourth": 4, "third": 3}] } >>> print collapse_json(text, indent=8) { "zero": [ "first", {"items": [[1, 2, 3, 4], [5, 6, 7, 8], 9, 10, [11, [12, [13, [14, 15]]]]], "second": 2, "fourth": 4, "third": 3} ] } >>> print collapse_json(text, indent=12) { "zero": [ "first", { "items": [[1, 2, 3, 4], [5, 6, 7, 8], 9, 10, [11, [12, [13, [14, 15]]]]], "second": 2, "fourth": 4, "third": 3 } ] } >>> print collapse_json(text, indent=16) { "zero": [ "first", { "items": [ [1, 2, 3, 4], [5, 6, 7, 8], 9, 10, [11, [12, [13, [14, 15]]]] ], "second": 2, "fourth": 4, "third": 3 } ] } 

De hecho, una de las cosas que YAML es mejor que JSON.

No puedo hacer que NoIndentEncoder funcione, pero puedo usar expresiones regulares en la cadena JSON …

 def collapse_json(text, list_length=5): for length in range(list_length): re_pattern = r'\[' + (r'\s*(.+)\s*,' * length)[:-1] + r'\]' re_repl = r'[' + ''.join(r'\{}, '.format(i+1) for i in range(length))[:-2] + r']' text = re.sub(re_pattern, re_repl, text) return text 

La pregunta es, ¿cómo realizo esto en una lista anidada?

Antes de:

 [ 0, "any", [ 2, 3 ] ] 

Después:

 [0, "any", [2, 3]] 

Esta solución no es tan elegante y genérica como las otras y no aprenderá mucho de ella, pero es rápida y simple.

 def custom_print(data_structure, indent): for key, value in data_structure.items(): print "\n%s%s:" % (' '*indent,str(key)), if isinstance(value, dict): custom_print(value, indent+1) else: print "%s" % (str(value)), 

Uso y salida:

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