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

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

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

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

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

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

I have found that when the following is run, python’s json module (included since 2.6) converts int dictionary keys to strings.

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

Is there any easy way to preserve the key as an int, without needing to parse the string on dump and load. I believe it would be possible using the hooks provided by the json module, but again this still requires parsing. Is there possibly an argument I have overlooked? cheers, chaz

Sub-question: Thanks for the answers. Seeing as json works as I feared, is there an easy way to convey key type by maybe parsing the output of dumps? Also I should note the code doing the dumping and the code downloading the json object from a server and loading it, are both written by me.


回答 0

这是可能困扰您的各种映射集合之间的细微差别之一。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对象…,然后使用那些键来访问映射中的值)。

This is one of those subtle differences among various mapping collections that can bite you. JSON treats keys as strings; Python supports distinct keys differing only in type.

In Python (and apparently in Lua) the keys to a mapping (dictionary or table, respectively) are object references. In Python they must be immutable types, or they must be objects which implement a __hash__ method. (The Lua docs suggest that it automatically uses the object’s ID as a hash/key even for mutable objects and relies on string interning to ensure that equivalent strings map to the same objects).

In Perl, Javascript, awk and many other languages the keys for hashes, associative arrays or whatever they’re called for the given language, are strings (or “scalars” in Perl). In perl $foo{1}, $foo{1.0}, and $foo{"1"} are all references to the same mapping in %foo — the key is evaluated as a scalar!

JSON started as a Javascript serialization technology. (JSON stands for JavaScript Object Notation.) Naturally it implements semantics for its mapping notation which are consistent with its mapping semantics.

If both ends of your serialization are going to be Python then you’d be better off using pickles. If you really need to convert these back from JSON into native Python objects I guess you have a couple of choices. First you could try (try: ... except: ...) to convert any key to a number in the event of a dictionary look-up failure. Alternatively, if you add code to the other end (the serializer or generator of this JSON data) then you could have it perform a JSON serialization on each of the key values — providing those as a list of keys. (Then your Python code would first iterate over the list of keys, instantiating/deserializing them into native Python objects … and then use those for access the values out of the mapping).


回答 1

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

No, there is no such thing as a Number key in JavaScript. All object properties are converted to String.

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

This can lead to some curious-seeming behaviours:

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

JavaScript Objects aren’t really proper mappings as you’d understand it in languages like Python, and using keys that aren’t String results in weirdness. This is why JSON always explicitly writes keys as strings, even where it doesn’t look necessary.


回答 2

或者,您也可以尝试在使用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解码回去后将要转换为字典的所有参数。

Alternatively you can also try converting dictionary to a list of [(k1,v1),(k2,v2)] format while encoding it using json, and converting it back to dictionary after decoding it back.


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

回答 3

回答您的子问题:

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

Answering your subquestion:

It can be accomplished by using 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

This function will also work for nested dicts and uses a dict comprehension.

If you want to to cast the values too, use:

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

Which tests the instance of the values and casts them only if they are strings objects (unicode to be exact).

Both functions assumes keys (and values) to be integers.

Thanks to:

How to use if/else in a dictionary comprehension?

Convert a string key to int in a Dictionary


回答 4

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

I’ve gotten bitten by the same problem. As others have pointed out, in JSON, the mapping keys must be strings. You can do one of two things. You can use a less strict JSON library, like demjson, which allows integer strings. If no other programs (or no other in other languages) are going to read it, then you should be okay. Or you can use a different serialization language. I wouldn’t suggest pickle. It’s hard to read, and is not designed to be secure. Instead, I’d suggest YAML, which is (nearly) a superset of JSON, and does allow integer keys. (At least PyYAML does.)


回答 5

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

import ast
ast.literal_eval(string)

Convert the dictionary to be string by using str(dict) and then convert it back to dict by doing this:

import ast
ast.literal_eval(string)

回答 6

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

Here is my solution! I used object_hook, it is useful when you have nested 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'}}

There is filter only for parsing json key to int. You can use int(v) if v.lstrip('-').isdigit() else v filter for json value too.


回答 7

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

I made a very simple extension of Murmel’s answer which I think will work on a pretty arbitrary dictionary (including nested) assuming it can be dumped by JSON in the first place. Any keys which can be interpreted as integers will be cast to int. No doubt this is not very efficient, but it works for my purposes of storing to and loading from json strings.

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

Assuming that all keys in the original dict are integers if they can be cast to int, then this will return the original dictionary after storing as a json. e.g.

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

回答 8

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

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

You can write your json.dumps by yourself, here is a example from djson: encoder.py. You can use it like this:

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