Чи можу я змусити JSON завантажуватися в OrdersDict?


427

Гаразд, тому я можу використовувати OrdersDict в json.dump. Тобто, OrriedDict може бути використаний як вхід до JSON.

Але чи можна його використовувати як вихід? Якщо так, як? У моєму випадку я хотів би перейти loadв OrdersDict, щоб я міг зберігати порядок ключів у файлі.

Якщо ні, чи є якесь вирішення?


Ніколи не намагався підтримувати порядок, хоча я, безумовно, бачу, як це було б корисно.
feathj

1
Так, у моєму випадку я подолаю розрив між різними мовами та програмами, і JSON працює дуже добре. Але впорядкування ключів - це проблема. Було б дивовижно просто встановити галочку, json.loadщоб використовувати OrdersDicts замість Dicts у Python.
c00kiemonster

3
Специфікація JSON визначає тип об'єкта як невпорядковані ключі ... очікуючи, що конкретний порядок клавіш є помилкою
Anentropic

3
Впорядкування ключів зазвичай не відповідає будь-яким функціональним вимогам. Це головним чином лише для читабельності людини. Якщо я просто хочу, щоб мій json був симпатичним друком, я не очікую, що жодне замовлення документа зміниться взагалі.
Соління

5
Це також допомагає уникнути великих git різниться!
Річард Раст

Відповіді:


609

Так, ти можеш. Вказавши object_pairs_hookаргумент на JSONDecoder . Насправді це саме той приклад, наведений у документації.

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

Ви можете передати цей параметр json.loads(якщо вам не потрібен примірник декодера для інших цілей), наприклад:

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

Використання json.loadробиться так само:

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

3
Я здивований. Документи кажуть, що object_pairs_hook викликається для кожного букваря, який декодується в пари. Чому це не створює новий OrdersDict для кожного запису в JSON?
Тім Кітінг

3
Гм ... Документи дещо неоднозначно сформульовані. Що вони означають, що "весь результат декодування всіх пар" буде переданий, для того, як список, до object_pairs_hook, а не "кожна пара буде передана в object_pairs_hook",
SingleNegationElimination

Але чи втрачає початковий порядок входу json?
SIslam

Здивувавшись, побачивши, що json.loadце не впорядковано за замовчуванням, але виглядає, що це лише дзеркальне відображення того, що робить сам json - не упорядковані {}, але []в json впорядковано так, як описано тут
кардамон

1
@RandomCertainty так, кожен раз, коли під час розбору джерела виникає об'єкт JSON, OrderedDictбуде використовуватися для нарощування отриманого значення python.
SingleNegationElimination

125

Проста версія для Python 2.7+

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

Або для Python 2.4 - 2.6

import simplejson as json
import ordereddict

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

4
А-а-а, але він не включає object_pairs_hook - саме тому вам все ще потрібен simplejson в 2.6. ;)
mjhm

8
Хочу відзначити , що simplejsonі ordereddictокремі бібліотеки , які вам потрібно встановити.
phunehehe

2
для python 2.7+: "імпортувати json, колекції" у коді, для python2.6- "встановити придатність python-pip" та "pip встановити
впорядкований вирок

Це набагато простіше і швидше вперед, ніж попередній метод з JSONDecoder.
Natim

Як не дивно, в pypy включений json не вдасться loads('{}', object_pairs_hook=OrderedDict).
Меттью Шинкель

37

Деякі чудові новини! З версії 3.6 реалізація cPython зберегла порядок вставки словників ( https://mail.python.org/pipermail/python-dev/2016-September/146327.html ). Це означає, що бібліотека json тепер замовлення зберігає за замовчуванням. Зверніть увагу на різницю в поведінці між python 3.5 та 3.6. Код:

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

У py3.5 отриманий порядок не визначено:

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

У реалізації cPython python 3.6:

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

Дійсно чудова новина полягає в тому, що це стало мовною специфікацією для python 3.7 (на відміну від деталей реалізації cPython 3.6+): https://mail.python.org/pipermail/python-dev/2017-December/151283 .html

Тож відповідь на ваше запитання тепер стає: оновлення до python 3.6! :)


1
Хоча я бачу таку саму поведінку, як і ви в наведеному прикладі, в CPython реалізації Python 3.6.4 json.loads('{"2": 2, "1": 1}')стає {'1': 1, '2': 2}для мене.
fuglede

1
@fuglede це схоже на dict.__repr__сортування клавіш, поки основне впорядкування зберігається. Іншими словами, json.loads('{"2": 2, "1": 1}').items()це dict_items([('2', 2), ('1', 1)])навіть якщо repr(json.loads('{"2": 2, "1": 1}'))є "{'1': 1, '2': 2}".
Саймон Шаретт

@SimonCharette Гм, може бути; Я фактично не можу відтворити своє власне спостереження в pkgs / main / win-64 :: python-3.6.4-h0c2934d_3 conda, тому це буде важко перевірити.
fuglede

Це насправді не дуже допомагає, оскільки "перейменування" клавіш все одно порушить порядок клавіш.
Хубро

7

Ви завжди можете виписати список ключів на додаток до скидання диктату, а потім реконструювати OrderedDictітерацію через список?


1
+1 для низькотехнологічного рішення. Я це зробив, коли вирішував ту саму проблему з YAML, але дублювати це все одно кульгаво, особливо коли основний формат зберігає порядок. Можливо, також має сенс уникнути втрати пар ключових значень, які знаходяться в дикті, але відсутні у списку клавіш, торкнувшись їх після всіх чітко упорядкованих елементів.
Mu Mind

2
Низькотехнологічне рішення також зберігає контекст, який інакше не обов'язково зберігається у експортованому форматі (IOW; хтось бачить JSON, і там нічого прямо не зазначено "ці ключі повинні залишатися в цьому порядку", якщо вони маніпулюють на ньому).
Бурштин

Що визначає, що список ключів, "скинутих", у правильному порядку? Що про вкладені дикти? Схоже, що і демпінг повинен впоратися з цим, і реконструкцію потрібно робити рекурсивно, використовуючи OrdereDicts.
мартіно

5

Окрім скидання впорядкованого списку ключів поряд зі словником, ще одне низькотехнологічне рішення, яке має перевагу бути явним, - скидати (упорядкований) список пар ключів і значень ordered_dict.items(); завантаження просте OrderedDict(<list of key-value pairs>). Це обробляє впорядкований словник, незважаючи на те, що JSON не має цього поняття (словники JSON не мають порядку).

Дійсно приємно скористатися тим, що jsonскидає OrdersDict у правильному порядку. Однак взагалі зайве важко і не обов'язково має сенс читати всі словники JSON як OrriedDict (через object_pairs_hookаргумент), тому явна конверсія лише словників, які потрібно замовити, має сенс теж.


4

Команда завантаження, що використовується зазвичай, буде працювати, якщо вказати параметр 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)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.