我能把JSON加载到OrderedDict中吗?

464

好的,我可以在json.dump中使用OrderedDict。也就是说,OrderedDict可以作为JSON的输入。

但是它能用作输出吗?如果可以,怎么做?在我的情况下,我想要load到一个OrderedDict中,以便保留文件中键的顺序。

如果不行,有没有什么解决办法?


2
是的,在我的情况下,我正在弥合不同语言和应用程序之间的差距,JSON非常有效。但键的排序有点问题。如果在Python中的json.load中有一个简单的勾选框可以使用OrderedDicts而不是Dicts,那将是很棒的。 - c00kiemonster
这相当令人讨厌。在JavaScript中(其中json是一个子集),键的顺序也不会被保留... - SingleNegationElimination
5
JSON规范将对象类型定义为无序键值对,期望特定的键值对顺序是一个错误。 - Anentropic
5
键的排序通常并不是因为任何功能上的要求,而主要是为了让人更易于阅读。如果我只想让我的JSON格式化美观,我并不希望其中的任何文档顺序发生改变。 - Pickles
8
它还可以帮助避免出现大的 git 差异! - Richard Rast
参见:https://dev59.com/HVcP5IYBdhLWcg3w0tM0 - dreftymac
6个回答

663

可以的,你可以通过在JSONDecoder中指定object_pairs_hook参数来实现。事实上,这正是官方文档中提供的示例。

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

如果您没有其他用途需要 Decoder 实例,可以将此参数传递给 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函数。为什么这不会为JSON中的每个记录创建一个新的OrderedDict呢? - Tim Keating
3
文件有些措辞含糊。它们的意思是“解码所有配对的整个结果”将按顺序作为列表传递给object_pairs_hook,而不是“每个配对都将传递给object_pairs_hook”。 - SingleNegationElimination
但是它会失去输入JSON的原始顺序吗? - SIslam
1
添加OrderedDict钩子是否会保持字典中更深层次的层级顺序? - Random Certainty
1
@RandomCertainty 是的,在解析源代码时每次遇到 JSON 对象,都将使用 OrderedDict 来构建生成的 Python 值。 - SingleNegationElimination
显示剩余2条评论

130

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 - 这就是为什么你在2.6中仍然需要simplejson的原因。 ;) - mjhm
8
请注意,“simplejson”和“ordereddict”是两个需要单独安装的库。 - phunehehe
2
对于Python 2.7+版本,请在代码中添加以下内容:"import json, collections"。对于Python 2.6-版本,请在系统中执行以下命令:"aptitude install python-pip"和"pip install ordereddict"。 - ZiTAL
@mjhm 我为什么会收到一个 TypeError: 'OrderedDict' object is not callable 错误? - Mike
@Mike -- 我不知道,虽然我很久没看过这个了。 - mjhm
显示剩余5条评论

49

好消息!自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))

在Python 3.5中,结果的顺序是未定义的:

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

在 Python 3.6 的 cPython 实现中:

{
    "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
虽然我在给定的示例中看到了与您相同的行为,但在Python 3.6.4的CPython实现中,json.loads('{"2": 2, "1": 1}')对我来说变成了{'1': 1, '2': 2} - fuglede
3
看起来像是dict.__repr__会对键进行排序,但底层的顺序仍保持不变。换句话说,即使repr(json.loads('{"2": 2, "1": 1}'))的结果是"{'1': 1, '2': 2}"json.loads('{"2": 2, "1": 1}').items()返回的结果仍然是dict_items([('2', 2), ('1', 1)]) - Simon Charette
@SimonCharette 嗯,可能是这样;实际上我无法在conda的pkgs/main/win-64::python-3.6.4-h0c2934d_3中重现自己的观察结果,所以这将很难测试。 - fuglede
这并没有太大帮助,因为“重命名”键仍会破坏键的顺序。 - Hubro
1
Python文档链接--文档提到“从Python 3.7开始,常规字典变得有序,因此在JSON生成和解析中不再需要指定'collections.OrderedDict',这意味着默认情况下load按正确顺序插入到字典中。” - user202729
@fuglede:听起来像是你使用的IPython版本对于显示排序字典键。 (这绝对不是dict.__repr__-在任何Python版本中都不会进行排序。) - user2357112

7

除了转储字典之外,您还可以编写键列表,并通过迭代该列表来重构OrderedDict?


1
+1 低技术解决方案。我在处理 YAML 相同问题时也这样做过,但是必须复制有点烦人,特别是底层格式保留顺序。可能还有意义避免丢失字典中存在但键列表中缺失的键值对,在所有明确排序的项目之后添加它们。 - Mu Mind
2
低技术方案还可以保留在导出格式中没有明确保留的上下文(即,如果某人对它进行操作,看到JSON并没有明确说明“这些键应该按照这个顺序保留”)。 - Amber
1
什么决定了“dumped”键的列表是正确的顺序?嵌套字典呢?看起来似乎需要处理两个转储,并且重建需要使用OrdereDict递归完成。 - martineau

6
通常使用的load命令将在你指定 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)

5
除了将有序键的列表与字典一起转储之外,另一个低技术解决方案是转储(有序)键值对列表ordered_dict.items();加载则是简单的OrderedDict(<list of key-value pairs>)。这处理了有序字典,尽管JSON没有这个概念(JSON字典没有顺序)。
利用json以正确顺序转储OrderedDict确实很好。然而,通常情况下,通过object_pairs_hook参数将所有JSON字典都读取为OrderedDict(即使不需要),可能会使得负担过重且意义不明确,因此只对必须有序的字典进行显式转换也是有意义的。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接