Python 的 json 模块,将 int 字典键转换为字符串

我发现在运行以下命令时,python 的 json 模块(自2.6以来包括在内)将 int 字典键转换为字符串。

>>> import json
>>> releases = {1: "foo-v0.1"}
>>> json.dumps(releases)
'{"1": "foo-v0.1"}'

有没有简单的方法可以将密钥保存为 int 类型,而不需要在转储和加载时解析字符串。 我相信使用 json 模块提供的 hook 是可能的,但是这仍然需要解析。 是不是有什么我忽略了的论点? 干杯 Chaz

小问题: 谢谢你的回答。既然 json 的工作方式和我担心的一样,那么有没有一种简单的方法可以通过解析转储的输出来传递键类型呢? 另外,我还应该注意到,执行转储的代码和从服务器下载 json 对象并加载它的代码都是由我编写的。

84650 次浏览

不,在 JavaScript 中没有数字键这种东西。所有的对象属性都被转换成字符串。

var a= {1: 'a'};
for (k in a)
alert(typeof k); // 'string'

这可能导致一些看似奇怪的行为:

a[999999999999999999999]= 'a'; // this even works on Array
alert(a[1000000000000000000000]); // 'a'
alert(a['999999999999999999999']); // fail
alert(a['1e+21']); // 'a'

JavaScript 对象实际上并不是像 Python 这样的语言中所理解的那样正确的映射,使用不是 String 的键会导致奇怪的结果。这就是为什么 JSON 总是显式地将键写成字符串,即使在看起来没有必要的地方也是如此。

这是各种映射集合之间的细微差别之一,可能会影响到您。JSON 将键视为字符串; Python 支持仅在类型上有区别的键。

在 Python 中(显然在 Lua 中) ,映射的键(分别是 dictionary 或 table)是对象引用。在 Python 中,它们必须是不可变的类型,或者它们必须是实现 __hash__方法的对象。(Lua 文档建议它自动使用对象的 ID 作为散列/键,即使对于可变对象也是如此,并且依赖字符串实习来确保等效的字符串映射到相同的对象)。

在佩尔,Javascript、 awk 和许多其他语言中,散列、关联数组或者其他任何对于给定语言的称呼的键都是字符串(在 Perl 中是“标量”)。在 perl$foo{1}, $foo{1.0}, and $foo{"1"}中,所有对 %foo中相同映射的引用-- 键是作为标量的 评估

JSON 最初是一种 Javascript 序列化技术。(JSON 代表 Java是的cript bjectNotion。)自然地,它实现了与其映射语义一致的映射符号的语义。

如果序列化的两端都是 Python,那么最好使用 pickles。如果您真的需要将这些内容从 JSON 转换回原生 Python 对象,我想您有两种选择。首先,在字典查找失败的情况下,您可以尝试(try: ... except: ...)将任何键转换为数字。或者,如果您将代码添加到另一端(这个 JSON 数据的序列化器或生成器) ,那么您可以让它对每个键值执行 JSON 序列化——提供这些键列表。(然后您的 Python 代码将首先迭代键列表,将它们实例化/反序列化为本机 Python 对象... ... 然后使用这些对象访问映射之外的值)。

我也被同样的问题困扰过。正如其他人指出的那样,在 JSON,映射键必须是字符串。你有两种选择。您可以使用不那么严格的 JSON 库,比如 Demjson,它允许使用整数字符串。如果没有其他程序(或其他语言中的程序)要读取它,那么您应该没有问题。或者可以使用不同的序列化语言。我不建议你吃腌黄瓜。很难读,而且是 不是为了安全而设计的。相反,我建议使用 YAML,它(几乎)是 JSON 的超集,并且允许整数键。(至少 PyYAML是这样。)

或者,您也可以尝试在使用 json 编码字典时将 dictionary 转换为[(k1,v1) ,(k2,v2)]格式的列表,并在解码后将其转换回 dictionary。


>>>> import json
>>>> json.dumps(releases.items())
'[[1, "foo-v0.1"]]'
>>>> releases = {1: "foo-v0.1"}
>>>> releases == dict(json.loads(json.dumps(releases.items())))
True
I believe this will need some more work like having some sort of flag to identify what all parameters to be converted to dictionary after decoding it back from json.

回答你的问题:

它可以通过使用 json.loads(jsonDict, object_hook=jsonKeys2int)来实现

def jsonKeys2int(x):
if isinstance(x, dict):
return {int(k):v for k,v in x.items()}
return x

这个函数也适用于嵌套的字词,并使用字词理解。

如果您也想强制转换值,请使用:

def jsonKV2int(x):
if isinstance(x, dict):
return {int(k):(int(v) if isinstance(v, unicode) else v) for k,v in x.items()}
return x

它测试值的实例,并且仅当它们是字符串对象时才强制转换它们(确切地说是 unicode)。

这两个函数都假定键(和值)是整数。

感谢:

如何在词典理解中使用 if/else?

在 Dictionary 中将字符串键转换为 int

[ NSFW ]你可以自己写你的 json.dumps,这里有一个来自 Djson: 编码器的例子。你可以这样使用它:

assert dumps({1: "abc"}) == '{1: "abc"}'

通过使用 str(dict)将字典转换为字符串,然后通过以下操作将其转换回 dict:

import ast
ast.literal_eval(string)

这是我的解决方案! 我使用的是 object_hook,它是有用的,当你有嵌套的 json

>>> import json
>>> json_data = '{"1": "one", "2": {"-3": "minus three", "4": "four"}}'
>>> py_dict = json.loads(json_data, object_hook=lambda d: {int(k) if k.lstrip('-').isdigit() else k: v for k, v in d.items()})


>>> py_dict
{1: 'one', 2: {-3: 'minus three', 4: 'four'}}

只有用于将 json 键解析为 int 的过滤器。您也可以使用 int(v) if v.lstrip('-').isdigit() else v过滤器来处理 json 值。

我对 Murmel 的回答做了一个非常简单的扩展,我认为它可以用于一个相当随意的字典(包括嵌套的) ,假设它可以被 JSON 转储。任何可以解释为整数的键将被强制转换为 int。毫无疑问,这不是非常有效,但是对于我存储到 json 字符串并从中加载的目的来说,它是有效的。

def convert_keys_to_int(d: dict):
new_dict = {}
for k, v in d.items():
try:
new_key = int(k)
except ValueError:
new_key = k
if type(v) == dict:
v = _convert_keys_to_int(v)
new_dict[new_key] = v
return new_dict

假设原始 dict 中的所有键都是整数(如果它们可以强制转换为 int 的话) ,那么这将在存储为 json 之后返回原始 dictionary。 例如:。

>>>d = {1: 3, 2: 'a', 3: {1: 'a', 2: 10}, 4: {'a': 2, 'b': 10}}
>>>convert_keys_to_int(json.loads(json.dumps(d)))  == d
True