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

Python's json module, converts int dictionary keys to strings

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

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

有什么简单的方法可以将键保留为int,而无需在转储和加载时解析字符串。
我相信可以使用json模块提供的钩子,但这仍然需要解析。
我可能会忽略一个论点吗?
欢呼声,查兹

子问题:
感谢您的回答。 看到json就像我所担心的那样工作,是否有一种简单的方法可以通过解析转储的输出来传达密钥类型?
我还要注意执行转储的代码以及从服务器下载json对象并加载它的代码都是我编写的。


这是可能困扰您的各种映射集合之间的细微差别之一。 JSON将键视为字符串; Python支持仅在类型上不同的独特键。

在Python中(显然在Lua中),映射的键(分别是字典或表)是对象引用。在Python中,它们必须是不可变的类型,或者它们必须是实现__hash__方法的对象。 (Lua文档建议,即使对于可变对象,它也会自动将对象的ID用作哈希/键,并依赖于字符串插值以确保等效的字符串映射到相同的对象)。

在Perl,Javascript,awk和许多其他语言中,哈希,关联数组的键或给定语言所调用的键都是字符串(或Perl中的"标量")。在perl $foo{1}, $foo{1.0}, and $foo{"1"}中,所有对%foo中相同映射的引用-键被评估为标量!

JSON从Java序列化技术开始。 (JSON表示JavaScript对象表示法。)自然地,它为其映射表示法实现了与其映射语义一致的语义。

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


不,JavaScript中没有数字键之类的东西。所有对象属性都将转换为String。

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

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

1
2
3
4
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进行编码的同时将字典转换为[(k1,v1),(k2,v2)]格式的列表,并在将其解码后将其转换回字典。

1
2
3
4
5
6
>>>> import json
>>>> json.dumps(releases.items())
    '[[1,"foo-v0.1"]]'
>>>> releases = {1:"foo-v0.1"}
>>>> releases == dict(json.loads(json.dumps(releases.items())))
     True


我相信这将需要做更多的工作,例如具有某种标志,以识别从json解码回去后要转换为字典的所有参数。


回答您的子问题:

可以使用json.loads(jsonDict, object_hook=jsonKeys2int)完成

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

此功能也适用于嵌套字典,并使用字典理解。

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

1
2
3
4
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?

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


我被同样的问题咬了。正如其他人指出的那样,在JSON中,映射键必须是字符串。您可以做两件事之一。您可以使用不太严格的JSON库,例如demjson,它允许整数字符串。如果没有其他程序(或其他语言的其他语言)无法读取它,那么您应该可以。或者,您可以使用其他序列化语言。我不建议泡菜。它很难阅读,并非旨在确保安全。相反,我建议使用YAML,它几乎是JSON的超集,并且确实允许整数键。 (至少PyYAML这样做。)


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

1
2
import ast
ast.literal_eval(string)

我对Murmel的答案做了一个非常简单的扩展,我认为它首先可以被JSON转储,因此可以在相当随意的字典(包括嵌套的字典)上使用。任何可以解释为整数的键都将转换为int。毫无疑问,这不是很有效,但是它可以实现我存储到json字符串和从json字符串加载的目的。

1
2
3
4
5
6
7
8
9
10
11
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

假设原始字典中的所有键都是整数(如果可以将它们强制转换为int),则在将其存储为json后将返回原始字典。
例如

1
2
3
>>>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

这是我的解决方案!我使用了object_hook,当您嵌套json时它很有用

1
2
3
4
5
6
>>> 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值。


您可以自己编写json.dumps,这是djson中的示例:encoded.py。您可以像这样使用它:

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