Kann ich JSON zum Laden in ein OrderedDict veranlassen?

426

Ok, damit ich ein OrderedDict in verwenden kann json.dump. Das heißt, ein OrderedDict kann als Eingabe für JSON verwendet werden.

Aber kann es als Ausgabe verwendet werden? Wenn das so ist, wie? In meinem Fall möchte ich loadein OrderedDict erstellen, damit ich die Reihenfolge der Schlüssel in der Datei beibehalten kann.

Wenn nicht, gibt es eine Problemumgehung?

c00kiemonster
quelle
Ich habe nie versucht, die Ordnung aufrechtzuerhalten, obwohl ich sicher sehen kann, wie nützlich es wäre.
Feathj
1
Ja, in meinem Fall überbrücke ich die Lücke zwischen verschiedenen Sprachen und Anwendungen, und JSON funktioniert sehr gut. Die Bestellung von Schlüsseln ist jedoch ein Problem. Es wäre fantastisch, ein einfaches Häkchen json.loadzu haben, um OrderedDicts anstelle von Dicts in Python zu verwenden.
c00kiemonster
3
Die JSON-Spezifikation definiert den Objekttyp als ungeordnete Schlüssel ... das
Erwarten
3
Die Schlüsselbestellung ist normalerweise nicht für funktionale Anforderungen geeignet. Es ist hauptsächlich für die menschliche Lesbarkeit. Wenn ich nur möchte, dass mein JSON hübsch gedruckt wird, erwarte ich keine Änderung der Dokumentreihenfolge.
Pickles
5
Es hilft auch, große Git-Unterschiede zu vermeiden!
Richard Rast

Antworten:

609

Ja, du kannst. Durch Angabe des object_pairs_hookArguments an JSONDecoder . Tatsächlich ist dies das genaue Beispiel in der Dokumentation.

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

Sie können diesen Parameter wie json.loadsfolgt übergeben (wenn Sie keine Decoder-Instanz für andere Zwecke benötigen):

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

Die Verwendung json.loaderfolgt auf die gleiche Weise:

>>> data = json.load(open('config.json'), object_pairs_hook=OrderedDict)
SingleNegationElimination
quelle
3
Ich bin ratlos. Die Dokumente sagen, dass der object_pairs_hook für jedes Literal aufgerufen wird, das paarweise dekodiert wird. Warum wird dadurch nicht für jeden Datensatz im JSON ein neues OrderedDict erstellt?
Tim Keating
3
Hmm ... die Dokumente sind etwas mehrdeutig formuliert. Was sie bedeuten, dass das "gesamte Ergebnis der Dekodierung aller Paare" in der Reihenfolge als Liste an " object_pairs_hookund nicht an" jedes Paar wird an object_pairs_hook übergeben "übergeben wird
SingleNegationElimination
Aber verliert die ursprüngliche Reihenfolge der Eingabe json?
Islam
War überrascht zu sehen, dass json.loades nicht standardmäßig geordnet bleibt, sondern nur das widerspiegelt, was json selbst tut - die {}sind ungeordnet, aber die []im json sind wie hier
Kardamom
1
@RandomCertainty Ja, jedes Mal, wenn ein JSON-Objekt beim Parsen der Quelle angetroffen OrderedDictwird, wird der resultierende Python-Wert erstellt.
SingleNegationElimination
125

Einfache Version für Python 2.7+

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

Oder für Python 2.4 bis 2.6

import simplejson as json
import ordereddict

my_ordered_dict = json.loads(json_str, object_pairs_hook=ordereddict.OrderedDict)
mjhm
quelle
4
Ahhh, aber der object_pairs_hook ist nicht enthalten - weshalb Sie in 2.6 immer noch simplejson benötigen. ;)
mjhm
8
Möchten Sie dies beachten simplejsonund ordereddictsind separate Bibliotheken, die Sie installieren müssen.
Phunehehe
2
für Python 2.7+: "Importieren von JSON, Sammlungen" im Code, für Python2.6 - "aptitude install python-pip" und "pip install orderdict" im System
ZiTAL
Dies ist viel einfacher und schneller als die vorherige Methode mit JSONDecoder.
Natim
Seltsamerweise wird bei Pypy der enthaltene JSON nicht funktionieren loads('{}', object_pairs_hook=OrderedDict).
Matthew Schinckel
37

Einige gute Neuigkeiten! Seit Version 3.6 hat die cPython-Implementierung die Einfügereihenfolge der Wörterbücher beibehalten ( https://mail.python.org/pipermail/python-dev/2016-September/146327.html ). Dies bedeutet, dass die JSON-Bibliothek jetzt standardmäßig die Reihenfolge beibehält. Beachten Sie den Unterschied im Verhalten zwischen Python 3.5 und 3.6. Der Code:

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

In py3.5 ist die resultierende Reihenfolge undefiniert:

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

In der cPython-Implementierung von Python 3.6:

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

Die wirklich gute Nachricht ist, dass dies ab Python 3.7 zu einer Sprachspezifikation geworden ist (im Gegensatz zu einem Implementierungsdetail von cPython 3.6+): https://mail.python.org/pipermail/python-dev/2017-December/151283 .html

Die Antwort auf Ihre Frage lautet nun: Upgrade auf Python 3.6! :) :)

Pelson
quelle
1
Obwohl ich das gleiche Verhalten wie Sie im angegebenen Beispiel sehe, json.loads('{"2": 2, "1": 1}')wird es in der CPython-Implementierung von Python 3.6.4 {'1': 1, '2': 2}für mich.
Fuglede
1
@fuglede Es sieht so aus, als würden dict.__repr__Schlüssel sortiert, während die zugrunde liegende Reihenfolge beibehalten wird. Mit anderen Worten, json.loads('{"2": 2, "1": 1}').items()ist dict_items([('2', 2), ('1', 1)])selbst wenn repr(json.loads('{"2": 2, "1": 1}'))ist "{'1': 1, '2': 2}".
Simon Charette
@ SimonCharette Hm, könnte sein; Ich kann meine eigene Beobachtung in condas pkgs / main / win-64 :: python-3.6.4-h0c2934d_3 nicht reproduzieren, daher ist dies schwer zu testen.
Fuglede
Dies hilft jedoch nicht wirklich viel, da das "Umbenennen" von Schlüsseln immer noch die Reihenfolge der Schlüssel ruiniert.
Hubro
7

Sie könnten immer die Liste der Schlüssel zusätzlich zum Dumping des Diktats aufschreiben und dann das rekonstruieren, OrderedDictindem Sie die Liste durchlaufen?

Bernstein
quelle
1
+1 für Low-Tech-Lösung. Ich habe das getan, wenn ich mich mit dem gleichen Problem mit YAML befasst habe, aber das Duplizieren ist etwas lahm, besonders wenn das zugrunde liegende Format die Ordnung beibehält. Es könnte auch sinnvoll sein, zu vermeiden, dass Schlüssel-Wert-Paare verloren gehen, die im Diktat enthalten sind, aber in der Liste der Schlüssel fehlen, und sie nach allen explizit geordneten Elementen anheften.
Mu Mind
2
Die Low-Tech-Lösung bewahrt auch den Kontext, der ansonsten nicht unbedingt im exportierten Format erhalten bleibt (IOW; jemand sieht JSON und es gibt dort nichts, was explizit besagt, dass "diese Schlüssel in dieser Reihenfolge bleiben sollten", wenn sie daran manipulieren).
Amber
Was bestimmt, dass die Liste der "gedumpten" Schlüssel in der richtigen Reihenfolge ist? Was ist mit verschachtelten Diktaten? Es scheint, als müsste sowohl das Dumping damit umgehen als auch die Rekonstruktion rekursiv mit OrdereDicts durchgeführt werden.
Martineau
5

Neben dem Speichern der geordneten Liste von Schlüsseln neben dem Wörterbuch besteht eine weitere Low-Tech-Lösung, die den Vorteil hat, explizit zu sein, darin, die (geordnete) Liste von Schlüssel-Wert-Paaren zu ordered_dict.items()sichern. Laden ist einfach OrderedDict(<list of key-value pairs>). Dies behandelt ein geordnetes Wörterbuch, obwohl JSON dieses Konzept nicht hat (JSON-Wörterbücher haben keine Reihenfolge).

Es ist in der Tat schön, die Tatsache auszunutzen, dass jsondas OrderedDict in der richtigen Reihenfolge ausgegeben wird. Im Allgemeinen ist es jedoch unnötig schwer und nicht unbedingt sinnvoll, alle JSON-Wörterbücher als OrderedDict (durch das object_pairs_hookArgument) lesen zu müssen. Daher ist auch eine explizite Konvertierung nur der Wörterbücher sinnvoll, die bestellt werden müssen.

Eric O Lebigot
quelle
4

Der normalerweise verwendete Ladebefehl funktioniert, wenn Sie den Parameter object_pairs_hook angeben :

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