Czy mogę zmusić JSON do załadowania do zamówienia OrDERDict?

427

Ok, więc mogę użyć OrDERDict w json.dump. Oznacza to, że OrdictDict może być użyty jako dane wejściowe do JSON.

Ale czy można go wykorzystać jako wynik? Jeśli tak to jak? W moim przypadku chciałbym przejść loaddo nakazu, aby móc zachować kolejność kluczy w pliku.

Jeśli nie, czy istnieje jakieś obejście?

c00kiemonster
źródło
Nigdy nie próbowałem utrzymać porządku, chociaż z pewnością widzę, jak by to było przydatne.
feathj
1
Tak, w moim przypadku wypełniam lukę między różnymi językami i aplikacjami, a JSON działa bardzo dobrze. Ale kolejność kluczy to trochę problem. Byłoby wspaniale mieć proste zaznaczenie, json.loadaby używać OrdersDicts zamiast Dicts w Pythonie.
c00kiemonster,
3
Specyfikacja JSON definiuje typ obiektu jako mający nieuporządkowane klucze ... oczekiwanie określonej kolejności kluczy jest błędem
Anentropic
3
Kolejność kluczy zwykle nie spełnia żadnych wymagań funkcjonalnych. Chodzi głównie o czytelność dla ludzi. Jeśli chcę tylko, aby mój json był ładnie wydrukowany, nie oczekuję, aby kolejność dokumentów w ogóle się zmieniła.
Pikle
5
Pomaga także uniknąć dużych różnic git!
Richard Rast,

Odpowiedzi:

609

Tak, możesz. Podając object_pairs_hookargument JSONDecoder . W rzeczywistości jest to dokładny przykład podany w dokumentacji.

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

Możesz przekazać ten parametr do json.loads(jeśli nie potrzebujesz instancji dekodera do innych celów):

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

Korzystanie json.loadodbywa się w ten sam sposób:

>>> data = json.load(open('config.json'), object_pairs_hook=OrderedDict)
SingleNegationElimination
źródło
3
Jestem zakłopotany. Dokumenty mówią, że wywoływany jest object_pairs_hook dla każdego literału, który jest dekodowany na pary. Dlaczego nie tworzy to nowego Uporządkowanego Dict dla każdego rekordu w JSON?
Tim Keating
3
Hmm ... dokumenty są nieco niejednoznaczne. Co to znaczy, że „cały wynik dekodowania wszystkich par” zostanie przekazany, w postaci listy, do object_pairs_hook, zamiast „każda para zostanie przekazana do object_pairs_hook,”
SingleNegationElimination
Ale czy traci pierwotną kolejność wejścia json?
SIslam,
Byłem zaskoczony, widząc, że json.loaddomyślnie nie zachowuje kolejności, ale wygląda na to, że odzwierciedla tylko to, co robi sam Json - {}są nieuporządkowane, ale []w Json uporządkowane zgodnie z opisem tutaj
kardamon
1
@RandomCertainty tak, za każdym razem, gdy napotkany zostanie obiekt JSON podczas analizowania źródła, OrderedDictzostanie użyty do zbudowania wynikowej wartości pytona.
SingleNegationElimination
125

Prosta wersja dla Python 2.7+

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

Lub dla Python 2.4 do 2.6

import simplejson as json
import ordereddict

my_ordered_dict = json.loads(json_str, object_pairs_hook=ordereddict.OrderedDict)
mjhm
źródło
4
Ahhh, ale nie obejmuje obiektu object_pairs_hook - dlatego wciąż potrzebujesz simplejson w 2.6. ;)
mjhm
8
Chcesz to zauważyć simplejsoni ordereddictsą to osobne biblioteki, które musisz zainstalować.
phunehehe
2
dla python 2.7+: „importuj json, kolekcje” w kodzie, dla python2.6- „aptitude install python-pip” i „pip install orderdict” w systemie
ZiTAL 30.01.12
Jest to o wiele łatwiejsze i szybsze niż poprzednia metoda z JSONDecoderem.
Natim,
O dziwo, w pypy, dołączony json nie powiedzie się loads('{}', object_pairs_hook=OrderedDict).
Matthew Schinckel,
37

Kilka świetnych wiadomości! Od wersji 3.6 implementacja cPython zachowała kolejność wstawiania słowników ( https://mail.python.org/pipermail/python-dev/2016-September/146327.html ). Oznacza to, że biblioteka json domyślnie zachowuje teraz porządek. Zwróć uwagę na różnicę w zachowaniu między Pythonem 3.5 i 3.6. Kod:

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

W py3.5 wynikowa kolejność jest niezdefiniowana:

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

W implementacji cPython Pythona 3.6:

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

Naprawdę świetną wiadomością jest to, że stała się specyfikacją języka od Pythona 3.7 (w przeciwieństwie do szczegółów implementacji cPython 3.6+): https://mail.python.org/pipermail/python-dev/2017-December/151283 .html

Odpowiedź na twoje pytanie brzmi teraz: uaktualnij do Pythona 3.6! :)

pelson
źródło
1
Chociaż widzę takie samo zachowanie jak w podanym przykładzie, w implementacji CPython Python 3.6.4 json.loads('{"2": 2, "1": 1}')staje się {'1': 1, '2': 2}dla mnie.
fuglede
1
@fuglede wygląda jak dict.__repr__sortuje klucze, podczas gdy podstawowe porządkowanie jest zachowane. Innymi słowy, json.loads('{"2": 2, "1": 1}').items()jest dict_items([('2', 2), ('1', 1)])nawet jeśli repr(json.loads('{"2": 2, "1": 1}'))jest "{'1': 1, '2': 2}".
Simon Charette
@ SimonCharette Hm, może być; Właściwie nie jestem w stanie odtworzyć własnej obserwacji w pkgs / main / win-64 :: python-3.6.4-h0c2934d_3 Condy, więc będzie to trudne do przetestowania.
fuglede
To jednak niewiele pomaga, ponieważ „zmiana nazwy” kluczy nadal psuje kolejność kluczy.
Hubro
7

Zawsze możesz wypisać listę kluczy oprócz zrzucenia dykta, a następnie zrekonstruować je OrderedDictpoprzez iterację po liście?

Bursztyn
źródło
1
+1 za rozwiązanie mało zaawansowane. Zrobiłem to, gdy zajmowałem się tym samym problemem z YAML, ale konieczność duplikowania jest trochę lamerska, szczególnie gdy podstawowy format zachowuje porządek. Może również mieć sens unikanie utraty par klucz-wartość, które są w dykcie, ale których nie ma na liście kluczy, odkładając je po wszystkich wyraźnie zamówionych elementach.
Mu Mind
2
Niski poziom technologiczny zachowuje także kontekst, który w innym przypadku niekoniecznie byłby zachowany w wyeksportowanym formacie (IOW; ktoś widzi JSON i nic tam nie mówi wprost „te klucze powinny pozostać w tej kolejności”, jeśli wykonują na nim manipulacje).
Amber
Co decyduje o tym, że lista kluczy „zrzuconych” jest w odpowiedniej kolejności? Co z zagnieżdżonymi dyktami? Wydaje się, że zarówno dumping musiałby sobie z tym poradzić, a odbudowa musiałaby zostać wykonana rekurencyjnie przy użyciu OrdereDicts.
martineau,
5

Oprócz zrzucania uporządkowanej listy kluczy obok słownika, innym rozwiązaniem o niskiej technologii, które ma tę zaletę, że jest jawne, jest zrzucenie (uporządkowanej) listy par klucz-wartość ordered_dict.items(); ładowanie jest proste OrderedDict(<list of key-value pairs>). To obsługuje uporządkowany słownik, mimo że JSON nie ma tej koncepcji (słowniki JSON nie mają kolejności).

Naprawdę miło jest skorzystać z faktu, że jsonzrzuty OragedDict są wykonywane we właściwej kolejności. Jednak generalnie niepotrzebnie ciężkie i niekoniecznie sensowne jest czytanie wszystkich słowników JSON jako OrDERDict (poprzez object_pairs_hookargument), więc sensowna jest także wyraźna konwersja tylko słowników, które należy zamówić.

Eric O Lebigot
źródło
4

Zwykle używane polecenie ładowania zadziała, jeśli podasz parametr hook_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
źródło