Bisakah saya meminta JSON memuat ke dalam OrderedDict?

428

Oke, jadi saya bisa menggunakan OrderedDict di json.dump. Artinya, OrderedDict dapat digunakan sebagai input ke JSON.

Tapi bisakah itu digunakan sebagai output? Kalau begitu bagaimana? Dalam kasus saya, saya ingin loadmenjadi OrderedDict sehingga saya dapat menjaga urutan kunci dalam file.

Jika tidak, apakah ada semacam solusi?

c00kiemonster
sumber
Tidak pernah mencoba menjaga ketertiban, walaupun saya pasti bisa melihat bagaimana itu akan berguna.
feathj
1
Ya, dalam kasus saya, saya menjembatani kesenjangan antara berbagai bahasa dan aplikasi, dan JSON bekerja dengan sangat baik. Namun pemesanan kunci agak menjadi masalah. Akan luar biasa memiliki tanda centang yang sederhana json.loaduntuk menggunakan OrderedDicts alih-alih Dicts dengan Python.
c00kiemonster
3
Spec JSON mendefinisikan tipe objek sebagai memiliki kunci yang tidak berurutan ... mengharapkan urutan kunci spesifik adalah kesalahan
Anentropic
3
Pemesanan kunci biasanya tidak untuk persyaratan fungsional apa pun. Ini terutama hanya untuk keterbacaan manusia. Jika saya hanya ingin json saya dicetak-cantik, saya tidak mengharapkan urutan dokumen berubah sama sekali.
Acar
5
Ini juga membantu menghindari diff git yang besar!
Richard Rast

Jawaban:

610

Ya kamu bisa. Dengan menentukan object_pairs_hookargumen ke JSONDecoder . Sebenarnya, ini adalah contoh tepat yang diberikan dalam dokumentasi.

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

Anda dapat meneruskan parameter ini ke json.loads(jika Anda tidak memerlukan turunan Dekoder untuk tujuan lain) seperti:

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

Penggunaan json.loaddilakukan dengan cara yang sama:

>>> data = json.load(open('config.json'), object_pairs_hook=OrderedDict)
SingleNegationElimination
sumber
3
Saya bingung. Docs mengatakan object_pairs_hook dipanggil untuk setiap literal yang diterjemahkan menjadi pasangan. Mengapa ini tidak membuat OrderedDict baru untuk setiap catatan di JSON?
Tim Keating
3
Hmm ... dokumennya agak ambigu. Apa yang mereka maksudkan dengan "seluruh hasil penguraian semua pasangan" akan dilewati, dalam urutan, sebagai daftar, untuk object_pairs_hook, daripada "setiap pasangan akan diteruskan ke object_pairs_hook,"
SingleNegationElimination
Tetapi apakah kehilangan urutan asli dari input json?
SIslam
Terkejut melihat bahwa json.loadtidak tetap diperintahkan oleh default, tapi terlihat seperti itu hanya mencerminkan apa yang JSON sendiri tidak - yang {}adalah unordered, tetapi []di json yang diperintahkan seperti yang dijelaskan di sini
kapulaga
1
@RandomCertainty ya, setiap kali objek JSON ditemukan saat sumber parsing, OrderedDictakan digunakan untuk membangun nilai python yang dihasilkan.
SingleNegationElimination
125

Versi sederhana untuk Python 2.7+

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

Atau untuk Python 2.4 hingga 2.6

import simplejson as json
import ordereddict

my_ordered_dict = json.loads(json_str, object_pairs_hook=ordereddict.OrderedDict)
mjhm
sumber
4
Ahhh, tapi itu tidak termasuk object_pairs_hook - itulah sebabnya Anda masih perlu simplejson di 2.6. ;)
mjhm
8
Ingin mencatat itu simplejsondan ordereddictmerupakan pustaka terpisah yang perlu Anda instal.
phunehehe
2
untuk python 2.7+: "import json, collections" dalam kode, untuk python2.6- "aptitude install python-pip" dan "pip install ordereddict" dalam sistem
ZiTAL
Ini jauh lebih mudah dan cepat daripada metode sebelumnya dengan JSONDecoder.
Natim
Anehnya, di pypy, json yang disertakan akan gagal loads('{}', object_pairs_hook=OrderedDict).
Matthew Schinckel
37

Berita bagus! Sejak versi 3.6 implementasi cPython telah mempertahankan urutan penyisipan kamus ( https://mail.python.org/pipermail/python-dev/2016-September/146327.html ). Ini berarti bahwa pustaka json sekarang mempertahankan pesanan secara default. Amati perbedaan perilaku antara python 3.5 dan 3.6. Kode:

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

Dalam py3.5 pesanan yang dihasilkan tidak terdefinisi:

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

Dalam implementasi cPython python 3.6:

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

Berita yang benar-benar hebat adalah bahwa ini telah menjadi spesifikasi bahasa pada python 3.7 (yang bertentangan dengan detail implementasi cPython 3.6+): https://mail.python.org/pipermail/python-dev/2017-December/151283 .html

Jadi jawaban untuk pertanyaan Anda sekarang menjadi: upgrade ke python 3.6! :)

Pelson
sumber
1
Meskipun saya melihat perilaku yang sama seperti Anda dalam contoh yang diberikan, dalam implementasi CPython dari Python 3.6.4, json.loads('{"2": 2, "1": 1}')menjadi {'1': 1, '2': 2}bagi saya.
fuglede
1
@fuglede sepertinya dict.__repr__tombol sortir sementara pemesanan yang mendasarinya dipertahankan. Dengan kata lain, json.loads('{"2": 2, "1": 1}').items()adalah dict_items([('2', 2), ('1', 1)])bahkan jika repr(json.loads('{"2": 2, "1": 1}'))ini "{'1': 1, '2': 2}".
Simon Charette
@SimonCharette Hm, bisa jadi; Saya sebenarnya tidak dapat mereproduksi pengamatan saya sendiri di pkgs kondominium / main / win-64 :: python-3.6.4-h0c2934d_3, jadi ini akan sulit untuk diuji.
fuglede
Ini tidak terlalu banyak membantu, karena kunci "penggantian nama" masih akan merusak urutan kunci.
Hubro
7

Anda selalu bisa menulis daftar kunci selain membuang dict, dan kemudian merekonstruksi OrderedDictdengan mengulangi melalui daftar?

Amber
sumber
1
+1 untuk solusi berteknologi rendah. Saya sudah melakukan itu ketika berhadapan dengan masalah yang sama dengan YAML, tetapi harus menduplikasi agak timpang, terutama ketika format yang mendasarinya menjaga ketertiban. Mungkin juga masuk akal untuk menghindari kehilangan pasangan kunci-nilai yang ada dalam dikt tetapi hilang dari daftar kunci, menempelkannya setelah semua item yang dipesan secara eksplisit.
Mu Mind
2
Solusi berteknologi rendah juga mempertahankan konteks yang tidak harus dilestarikan dalam format yang diekspor (TKI; seseorang melihat JSON dan tidak ada yang secara eksplisit menyatakan "kunci-kunci ini harus tetap dalam urutan ini" jika mereka memanipulasi itu).
Amber
Apa yang menentukan bahwa daftar kunci "dibuang" dalam urutan yang benar? Bagaimana dengan dikte bersarang? Sepertinya kedua dumping perlu menangani itu dan rekonstruksi perlu dilakukan secara rekursif menggunakan OrdereDicts.
martineau
5

Selain membuang daftar kunci yang dipesan di samping kamus, solusi berteknologi rendah lainnya, yang memiliki keuntungan menjadi eksplisit, adalah membuang daftar pasangan kunci-nilai (dipesan) ordered_dict.items(); memuat itu sederhana OrderedDict(<list of key-value pairs>). Ini menangani kamus yang dipesan meskipun faktanya JSON tidak memiliki konsep ini (kamus JSON tidak memiliki urutan).

Memang menyenangkan untuk mengambil keuntungan dari kenyataan bahwa jsondump OrderedDict dalam urutan yang benar. Namun, secara umum tidak perlu berat dan tidak berarti harus membaca semua kamus JSON sebagai OrderedDict (melalui object_pairs_hookargumen), jadi konversi eksplisit hanya kamus yang harus dipesan masuk akal juga.

Eric O Lebigot
sumber
4

Perintah muat yang biasanya digunakan akan bekerja jika Anda menentukan parameter 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
sumber