Puis-je charger JSON dans un OrderedDict?


428

Ok, donc je peux utiliser un OrderedDict dans json.dump. Autrement dit, un OrderedDict peut être utilisé comme entrée pour JSON.

Mais peut-il être utilisé comme sortie? Si c'est le cas, comment? Dans mon cas, j'aimeraisload entrer dans un OrderedDict afin que je puisse conserver l'ordre des clés dans le fichier.

Sinon, existe-t-il une sorte de solution?


Je n'ai jamais essayé de maintenir l'ordre, même si je peux certainement voir comment cela serait utile.
feathj

1
Oui, dans mon cas, je comble le fossé entre les différentes langues et applications, et JSON fonctionne très bien. Mais la commande des clés est un peu problématique. Ce serait génial d'avoir un simple à cocher json.loadpour utiliser OrderedDicts au lieu de Dicts en Python.
c00kiemonster

3
La spécification JSON définit le type d'objet comme ayant des clés non ordonnées ... s'attendre à un ordre de clé spécifique est une erreur
Anentropic

3
L'ordre des clés n'est généralement pas destiné à des exigences fonctionnelles. C'est principalement juste pour la lisibilité humaine. Si je veux juste que mon json soit assez imprimé, je ne m'attends pas à ce que l'ordre des documents change du tout.
Pickles

5
Cela permet également d'éviter les gros différends git!
Richard Rast

Réponses:


610

Oui, vous pouvez. En spécifiant l' object_pairs_hookargument à JSONDecoder . En fait, c'est l'exemple exact donné dans la documentation.

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

Vous pouvez passer ce paramètre à json.loads(si vous n'avez pas besoin d'une instance de Decoder à d'autres fins) comme ceci:

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

L'utilisation json.loadse fait de la même manière:

>>> data = json.load(open('config.json'), object_pairs_hook=OrderedDict)

3
Je suis perplexe. Les documents indiquent que le object_pairs_hook est appelé pour chaque littéral décodé en paires. Pourquoi cela ne crée-t-il pas un nouveau OrderedDict pour chaque enregistrement dans le JSON?
Tim Keating du

3
Hmm ... les documents sont formulés de manière quelque peu ambiguë. Ce qu'ils veulent dire, c'est que "le résultat complet du décodage de toutes les paires" sera transmis, dans l'ordre, sous forme de liste, à object_pairs_hook, plutôt que "chaque paire sera transmise à object_pairs_hook,"
SingleNegationElimination

Mais est perd l'ordre d'origine de l'entrée json?
SIslam

A été surpris de voir que json.loadcela ne le maintient pas ordonné par défaut, mais il semble que cela ne fait que refléter ce que fait json lui-même - les commandes ne {}sont pas ordonnées, mais les commandes []dans le json sont commandées comme décrit ici
cardamome

1
@RandomCertainty yes, chaque fois qu'un objet JSON est rencontré lors de l'analyse de la source, OrderedDictsera utilisé pour créer la valeur python résultante.
SingleNegationElimination

125

Version simple pour Python 2.7+

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

Ou pour Python 2.4 à 2.6

import simplejson as json
import ordereddict

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

4
Ahhh, mais il n'inclut pas le object_pairs_hook - c'est pourquoi vous avez toujours besoin de simplejson en 2.6. ;)
mjhm

8
Vous voulez le noter simplejsonet ce ordereddictsont des bibliothèques distinctes que vous devez installer.
phunehehe

2
pour python 2.7+: "import json, collections" dans le code, pour python2.6- "aptitude install python-pip" et "pip install orderdict" dans le système
ZiTAL

C'est beaucoup plus facile et plus rapide que la méthode précédente avec JSONDecoder.
Natim

Bizarrement, dans Pypy, le json inclus échouera loads('{}', object_pairs_hook=OrderedDict).
Matthew Schinckel

37

De bonnes nouvelles! Depuis la version 3.6, l'implémentation de cPython a conservé l'ordre d'insertion des dictionnaires ( https://mail.python.org/pipermail/python-dev/2016-September/146327.html ). Cela signifie que la bibliothèque json conserve désormais l'ordre par défaut. Observez la différence de comportement entre python 3.5 et 3.6. Le code:

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

Dans py3.5, l'ordre résultant n'est pas défini:

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

Dans l'implémentation cPython de python 3.6:

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

La très bonne nouvelle est que c'est devenu une spécification de langage à partir de python 3.7 (par opposition à un détail d'implémentation de cPython 3.6+): https://mail.python.org/pipermail/python-dev/2017-December/151283 .html

La réponse à votre question devient alors: passer à python 3.6! :)


1
Bien que je vois le même comportement que vous dans l'exemple donné, dans l'implémentation CPython de Python 3.6.4, cela json.loads('{"2": 2, "1": 1}')devient {'1': 1, '2': 2}pour moi.
fuglede

1
@fuglede il ressemble à des dict.__repr__clés de tri tandis que l'ordre sous-jacent est préservé. En d'autres termes, json.loads('{"2": 2, "1": 1}').items()c'est dict_items([('2', 2), ('1', 1)])même si repr(json.loads('{"2": 2, "1": 1}'))c'est le cas "{'1': 1, '2': 2}".
Simon Charette

@SimonCharette Hm, pourrait être; Je ne suis pas en mesure de reproduire ma propre observation dans pkgs / main / win-64 :: python-3.6.4-h0c2934d_3 de conda, ce sera donc difficile à tester.
fuglede

Cela n'aide pas vraiment beaucoup cependant, car "renommer" les clés ruinera toujours l'ordre des clés.
Hubro

7

Vous pouvez toujours écrire la liste des clés en plus de vider le dict, puis reconstruire le OrderedDicten itérant dans la liste?


1
+1 pour une solution low-tech. Je l'ai fait lorsque je traite le même problème avec YAML, mais avoir à dupliquer est un peu boiteux, surtout lorsque le format sous-jacent préserve l'ordre. Il pourrait également être judicieux d'éviter de perdre des paires clé-valeur qui sont dans le dict mais qui manquent dans la liste des clés, en les clouant après tous les éléments explicitement ordonnés.
Mu Mind

2
La solution low tech préserve également le contexte qui n'est pas nécessairement conservé dans le format exporté (IOW; quelqu'un voit JSON et il n'y a rien là indiquant explicitement "ces clés doivent rester dans cet ordre" si elles font des manipulations dessus).
Ambre

Qu'est-ce qui détermine que la liste des clés "vidées" est dans le bon ordre? Et les dict imbriqués? Il semble que le dumping devrait être géré à la fois et que la reconstruction devrait être effectuée de manière récursive en utilisant l' OrdereDictart.
martineau

5

En plus de vider la liste ordonnée de clés à côté du dictionnaire, une autre solution de faible technologie, qui a l'avantage d'être explicite, consiste à vider la liste (ordonnée) de paires clé-valeur ordered_dict.items(); le chargement est simple OrderedDict(<list of key-value pairs>). Cela gère un dictionnaire ordonné malgré le fait que JSON n'a pas ce concept (les dictionnaires JSON n'ont pas d'ordre).

Il est en effet agréable de profiter du fait que les jsondumps OrderedDict dans le bon ordre. Cependant, il est en général inutilement lourd et pas nécessairement significatif d'avoir à lire tous les dictionnaires JSON en tant que OrderedDict (à travers l' object_pairs_hookargument), donc une conversion explicite des seuls dictionnaires qui doivent être ordonnés est également logique.


4

La commande de chargement normalement utilisée fonctionnera si vous spécifiez le paramètre 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)
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.