【问题标题】:Python's json module, converts int dictionary keys to stringsPython 的 json 模块,将 int 字典键转换为字符串
【发布时间】:2025-12-12 19:10:01
【问题描述】:

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

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

是否有任何简单的方法可以将密钥保留为 int,而无需在转储和加载时解析字符串。 我相信使用 json 模块提供的钩子是可能的,但这仍然需要解析。 有没有可能我忽略了一个论点? 干杯,伙计

子问题: 感谢您的回答。看到j​​son像我担心的那样有效,有没有一种简单的方法可以通过解析转储的输出来传达密钥类型? 另外我应该注意进行转储的代码和从服务器下载 json 对象并加载它的代码都是由我编写的。

【问题讨论】:

  • json 键必须是字符串

标签: python json


【解决方案1】:

不,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 等语言中所理解的那样,并且使用不是字符串的键会导致奇怪。这就是为什么 JSON 总是将键显式地写为字符串,即使它看起来没有必要。

【讨论】:

  • 为什么999999999999999999999 不转换为'999999999999999999999'
  • @PiotrDobrogost JavaScript(像许多语言一样)不能存储任意大的数字。 Number 类型是一个 IEEE 754 double 浮点值:您得到 53 位尾数,因此您最多可以存储 2⁵³ (9007199254740992) 整数精度;除此之外,整数将四舍五入为其他值(因此 9007199254740993 === 9007199254740992)。 999999999999999999999 舍入为 1000000000000000000000,默认toString 表示为1e+21
【解决方案2】:

这是各种映射集合之间的细微差别之一,可能会咬你一口。 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 代表 JavaScript Object Notation。)它自然地为其映射符号实现语义符合其映射语义。

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

【讨论】:

  • 谢谢。不幸的是,我不能使用 Pickle,但是您对列表的想法很棒。现在将实施,为这个想法喝彩。
  • (顺便提一下,在 Python 1 中,1L(长整数)和 1.0 映射到同一个键;但“1”(字符串)不映射到与 1(整数)或 1.0 相同的键(浮点数)或 1L(长整数)。
  • 谨慎使用 Pickle 的建议。 Pickle 可能导致任意代码执行,因此如果您要反序列化的数据源本身不可信,您应该坚持使用像 JSON 这样的“安全”序列化协议。另请记住,随着项目范围的扩大,有时您期望只能获得可信输入的功能开始获得用户提供的输入,并且并不总是重新考虑安全性考虑。
【解决方案3】:

我也被同样的问题所困扰。正如其他人指出的那样,在 JSON 中,映射键必须是字符串。你可以做两件事之一。您可以使用不那么严格的 JSON 库,例如 demjson,它允许使用整数字符串。如果没有其他程序(或其他语言的其他程序)不会阅读它,那么您应该没问题。或者您可以使用不同的序列化语言。我不建议泡菜。很难阅读,是not designed to be secure。相反,我建议 YAML,它(几乎)是 JSON 的超集,并且确实允许整数键。 (至少PyYAML 可以。)

【讨论】:

    【解决方案4】:

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

    
    >>>> 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对象的好解决方案!
    【解决方案5】:

    回答你的子问题:

    可以通过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)时强制转换它们。

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

    感谢:

    How to use if/else in a dictionary comprehension?

    Convert a string key to int in a Dictionary

    【讨论】:

    • 这很棒。在我的情况下,酸洗不能使用,所以我使用 JSON 通过转换为 byte_array 来保存对象的内容,以便我可以使用压缩。我有混合键,所以我只是修改了您的示例以在键不可转换为 int 时忽略 ValueError
    • 这仅在您希望所有键都是整数时才有效,对吧?如果 OP 抛出一个不可转换为 int 的密钥,这将抛出一个 ValueError
    • 对,最后一句关于这个假设。
    【解决方案6】:

    你可以自己写你的json.dumps,这里有一个来自djson的例子:encoder.py。你可以这样使用它:

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

    【讨论】:

      【解决方案7】:

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

      import ast
      ast.literal_eval(string)
      

      【讨论】:

      • 这是天才解决方案
      【解决方案8】:

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

      【讨论】:

        【解决方案9】:

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

        【讨论】: