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?

c00kiemonster
la source
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)
SingleNegationElimination
la source
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)
mjhm
la source
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! :)

pelson
la source
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?

ambre
la source
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.

Eric O Lebigot
la source
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)
ntg
la source