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


130

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

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

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

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


23
json键必须为字符串
-tonfa

Answers:


86

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

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

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

JSON是从Javascript序列化技术开始的。(JSON代表Ĵ AVA 小号 CRIPT ö bject Ñ浮选。)当然它实现为它的映射符号的语义这与它的映射语义一致。

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


1
感谢那。不幸的是,我不能使用Pickle,但是您对清单的想法很棒。将立即实施,为这个想法鼓舞。
查尔斯·里奇2009年

1
(顺便说一下,在Python 1中,1L(长整数)和1.0映射到相同的键;但是“ 1”(字符串)并不映射到与1(整数)或1.0(浮点数)或1L(长整数)相同的键。 )
Jim Dennis 2015年

5
建议不要使用Pickle。Pickle可能导致任意代码执行,因此,如果要反序列化的数据源本来就不可信,则应坚持使用“安全”序列化协议(如JSON)。还请记住,随着项目范围的扩大,有时您期望的功能只会获得可信的输入,而开始获得用户提供的输入,并且安全注意事项并非总是会重新考虑。
AusIV '16

56

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

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总是显式地将键写为字符串的原因,即使在不需要的地方也是如此。


1
为什么不999999999999999999999转换为'999999999999999999999'
Piotr Dobrogost '16

4
@PiotrDobrogost JavaScript(像许多语言一样)不能存储任意大的数字。该Number类型是IEEE 754双浮点值:您可以得到53位的尾数,因此您最多可以存储2³³(9007199254740992)的整数精度;超出该整数将舍入为其他值(因此9007199254740993 === 9007199254740992)。999999999999999999999舍入为1000000000000000000000,其默认toString表示形式为1e+21
bobince

22

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


>>>> 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解码回去后将要转换为字典的所有参数。


没有嵌套dict对象的dict对象的好解决方案!
汤Tom(Tom Yu)

15

回答您的子问题:

可以通过使用 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?

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


太好了 在我的情况下,不能使用酸洗,所以我通过转换为byte_array来使用JSON保存对象的内脏,以便可以使用压缩。我有混合的密钥,所以我刚刚修改了您的示例,以在密钥不可转换为int时忽略ValueError
minillinim 18-10-24

11

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


2

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

import ast
ast.literal_eval(string)

1

这是我的解决方案!我用过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值。


1

我对Murmel的答案做了一个非常简单的扩展,我认为它可以在相当随意的字典(包括嵌套字典)上工作,前提是它首先可以被JSON转储。任何可以解释为整数的键都将转换为int。毫无疑问,这不是很有效,但是它可以实现我存储到json字符串和从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

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

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

-1

你可以写你json.dumps自己,这里是从例如djsonencoder.py。您可以像这样使用它:

assert dumps({1: "abc"}) == '{1: "abc"}'
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.