¿Puedo hacer que JSON se cargue en un OrderedDict?

428

Ok, entonces puedo usar un OrderedDict en json.dump. Es decir, un OrderedDict se puede usar como entrada para JSON.

Pero, ¿se puede usar como salida? ¿Si es así, cómo? En mi caso, me gustaría loadingresar en un OrderedDict para poder mantener el orden de las claves en el archivo.

Si no, ¿hay algún tipo de solución?

c00kiemonster
fuente
Nunca traté de mantener el orden, aunque ciertamente puedo ver cómo sería útil.
feathj
1
Sí, en mi caso estoy cerrando la brecha entre diferentes idiomas y aplicaciones, y JSON funciona muy bien. Pero el orden de las claves es un problema. Sería increíble tener un simple json.loadpara usar OrderedDicts en lugar de Dicts en Python.
c00kiemonster
3
La especificación JSON define el tipo de objeto como que tiene claves desordenadas ... esperar que un orden de claves específico sea un error
Anentropic
3
El pedido de claves no suele ser para ningún tipo de requisitos funcionales. Es principalmente para la legibilidad humana. Si solo quiero que mi json esté bien impreso, no espero que cambie el orden de los documentos.
Pickles
55
¡También ayuda a evitar grandes diferencias de git!
Richard Rast

Respuestas:

610

Sí tu puedes. Al especificar el object_pairs_hookargumento a JSONDecoder . De hecho, este es el ejemplo exacto dado en la documentación.

>>> json.JSONDecoder(object_pairs_hook=collections.OrderedDict).decode('{"foo":1, "bar": 2}')
OrderedDict([('foo', 1), ('bar', 2)])
>>> 

Puede pasar este parámetro a json.loads (si no necesita una instancia de Decoder para otros fines) así:

>>> import json
>>> from collections import OrderedDict
>>> data = json.loads('{"foo":1, "bar": 2}', object_pairs_hook=OrderedDict)
>>> print json.dumps(data, indent=4)
{
    "foo": 1,
    "bar": 2
}
>>> 

El uso json.loadse realiza de la misma manera:

>>> data = json.load(open('config.json'), object_pairs_hook=OrderedDict)
SingleNegationElimination
fuente
3
Estoy perplejo Los documentos dicen que se llama a object_pairs_hook para cada literal que se decodifica en pares. ¿Por qué esto no crea un nuevo OrderedDict para cada registro en el JSON?
Tim Keating
3
Hmm ... los documentos están redactados de manera algo ambigua. Lo que quieren decir es que el "resultado completo de la decodificación de todos los pares" se pasará, en orden, como una lista, en object_pairs_hooklugar de "cada par se pasará a object_pairs_hook",
SingleNegationElimination
¿Pero se pierde el orden original de la entrada json?
SIslam
Me sorprendió ver que json.loadno lo mantiene ordenado de manera predeterminada, pero parece que solo refleja lo que hace json en sí mismo: {}están desordenados, pero []en json están ordenados como se describe aquí
cardamomo el
1
@RandomCertainty sí, cada vez que se encuentre un objeto JSON al analizar el origen, OrderedDictse usará para generar el valor resultante de python.
SingleNegationElimination
125

Versión simple para Python 2.7+

my_ordered_dict = json.loads(json_str, object_pairs_hook=collections.OrderedDict)

O para Python 2.4 a 2.6

import simplejson as json
import ordereddict

my_ordered_dict = json.loads(json_str, object_pairs_hook=ordereddict.OrderedDict)
mjhm
fuente
44
Ahhh, pero no incluye el object_pairs_hook, por lo que todavía necesitas simplejson en 2.6. ;)
mjhm
8
Desea tener en cuenta eso simplejsony ordereddictson bibliotecas separadas que necesita instalar.
phunehehe
2
para python 2.7+: "import json, collections" en código, para python2.6- "aptitude install python-pip" y "pip install ordersdict" en el sistema
ZiTAL
Esto es mucho más fácil y rápido que el método anterior con JSONDecoder.
Natim
Curiosamente, en pypy, el json incluido fallará loads('{}', object_pairs_hook=OrderedDict).
Matthew Schinckel
37

Algunas buenas noticias! Desde la versión 3.6, la implementación de cPython ha preservado el orden de inserción de los diccionarios ( https://mail.python.org/pipermail/python-dev/2016-September/146327.html ). Esto significa que la biblioteca json ahora conserva el orden de forma predeterminada. Observe la diferencia de comportamiento entre python 3.5 y 3.6. El código:

import json
data = json.loads('{"foo":1, "bar":2, "fiddle":{"bar":2, "foo":1}}')
print(json.dumps(data, indent=4))

En py3.5 el orden resultante no está definido:

{
    "fiddle": {
        "bar": 2,
        "foo": 1
    },
    "bar": 2,
    "foo": 1
}

En la implementación de cPython de python 3.6:

{
    "foo": 1,
    "bar": 2,
    "fiddle": {
        "bar": 2,
        "foo": 1
    }
}

La gran noticia es que esto se ha convertido en una especificación de lenguaje a partir de python 3.7 (en oposición a un detalle de implementación de cPython 3.6+): https://mail.python.org/pipermail/python-dev/2017-December/151283 .html

Entonces la respuesta a su pregunta ahora es: ¡actualice a Python 3.6! :)

pelson
fuente
1
Aunque veo el mismo comportamiento que usted en el ejemplo dado, en la implementación de CPython de Python 3.6.4, se json.loads('{"2": 2, "1": 1}')vuelve {'1': 1, '2': 2}para mí.
fuglede
1
@fuglede parece que dict.__repr__ordena las teclas mientras se conserva el orden subyacente. En otras palabras, json.loads('{"2": 2, "1": 1}').items()es dict_items([('2', 2), ('1', 1)])incluso si repr(json.loads('{"2": 2, "1": 1}'))es "{'1': 1, '2': 2}".
Simon Charette
@SimonCharette Hm, podría ser; En realidad, no puedo reproducir mi propia observación en pkgs / main / win-64 :: python-3.6.4-h0c2934d_3 de conda, por lo que será difícil de probar.
fuglede
Sin embargo, esto realmente no ayuda mucho, ya que "cambiar el nombre" de las teclas seguirá arruinando el orden de las teclas.
Hubro
7

¿Siempre podría escribir la lista de claves además de eliminar el dict y luego reconstruirlo OrderedDictiterando a través de la lista?

Ámbar
fuente
1
+1 para solución de baja tecnología. Lo he hecho al tratar el mismo problema con YAML, pero tener que duplicar es un poco lamentable, especialmente cuando el formato subyacente conserva el orden. También podría tener sentido evitar perder pares clave-valor que están en el dict pero que faltan en la lista de claves, agregándolos después de todos los elementos ordenados explícitamente.
Mu Mind
2
La solución de baja tecnología también conserva el contexto que de otro modo no se conserva necesariamente en el formato exportado (IOW; alguien ve JSON y no hay nada allí que indique explícitamente "estas teclas deben permanecer en este orden" si hacen manipulaciones en él).
Ámbar
¿Qué determina que la lista de claves "volcadas" esté en el orden correcto? ¿Qué pasa con los dictados anidados? Parece que tanto el vertido necesitaría manejar eso como la reconstrucción debería hacerse recursivamente usando OrdereDicts.
Martineau
5

Además de volcar la lista ordenada de claves junto con el diccionario, otra solución de baja tecnología, que tiene la ventaja de ser explícita, es volcar la lista (ordenada) de pares clave-valor ordered_dict.items(); cargar es un simpleOrderedDict(<list of key-value pairs>) . Esto maneja un diccionario ordenado a pesar de que JSON no tiene este concepto (los diccionarios JSON no tienen orden).

De hecho, es bueno aprovechar el hecho de que jsonvolca el OrderedDict en el orden correcto. Sin embargo, en general es innecesariamente pesado y no necesariamente significativo tener que leer todos los diccionarios JSON como un OrderedDict (a través del object_pairs_hookargumento), por lo que una conversión explícita de solo los diccionarios que deben ordenarse también tiene sentido.

Eric O Lebigot
fuente
4

El comando de carga usado normalmente funcionará si especifica el parámetro object_pairs_hook :

import json
from  collections import OrderedDict
with open('foo.json', 'r') as fp:
    metrics_types = json.load(fp, object_pairs_hook=OrderedDict)
ntg
fuente