json.dump和json.dumps对于JSON编码器产生不同的结果

3
我有一个这样格式的字符串:
d = {'details': {'hawk_branch': {'tandem': ['4210bnd72']}, 'uclif_branch': {'tandem': ['e2nc712nma89', '23s24212', '12338cm82']}}}

我想以这种格式将它写入文件,将列表转换为字典,并为列表中的每个值添加单词 value 作为键,因此 {'tandem': ['4210bnd72']} 应变成

  "tandem": {
    "value": "4210bnd72"
  }

这是预期的输出文件:

{
  "details": {

    "hawk_branch": {
      "tandem": {
        "value": "4210bnd72"
      }
    },
    "uclif_branch": {
      "tandem": {
        "value": "e2nc712nma89",
        "value": "23s24212",
        "value": "12338cm82",
      }
    }
    }
}

我在这里提出了一个问题,有人回答使用json.JSONEncoder

class restore_value(json.JSONEncoder):
    def encode(self, o):
        if isinstance(o, dict):
            return '{%s}' % ', '.join(': '.join((json.encoder.py_encode_basestring(k), self.encode(v))) for k, v in o.items())
        if isinstance(o, list):
            return '{%s}' % ', '.join('"value": %s' % self.encode(v) for v in o)
        return super().encode(o)

使用以上编码器,如果输入为:
d = {'details': {'hawk_branch': {'tandem': ['4210bnd72']}, 'uclif_branch': {'tandem': ['e2nc712nma89', '23s24212', '12338cm82']}}}

输出将变为:
print(json.dumps(d, cls=restore_value))
{"details": {"hawk_branch": {"tandem": {"value": "4210bnd72"}}, "uclif_branch": {"tandem": {"value": "e2nc712nma89", "value": "23s24212", "value": "12338cm82"}}}}

这正是我想要的,但现在我希望能将其写入文件中。

with open("a.json", "w") as f:
    json.dump(d, f, cls=restore_value)

但是它的写入方式与json.dumps输出的方式不同。

期望的输出结果如下:

{"details": {"hawk_branch": {"tandem": {"value": "4210bnd72"}}, "uclif_branch": {"tandem": {"value": "e2nc712nma89", "value": "23s24212", "value": "12338cm82"}}}}

我得到的输出结果:

{"details": {"hawk_branch": {"tandem": ["4210bnd72"]}, "uclif_branch": {"tandem": ["e2nc712nma89", "23s24212", "12338cm82"]}}}

请问为什么即使我使用编码器,它也以不同的方式写入文件?

复现方法:

复制以下代码并在Python 3中运行:

import json


class restore_value(json.JSONEncoder):
    def encode(self, o):
        if isinstance(o, dict):
            return '{%s}' % ', '.join(': '.join((json.encoder.py_encode_basestring(k), self.encode(v))) for k, v in o.items())
        if isinstance(o, list):
            return '{%s}' % ', '.join('"value": %s' % self.encode(v) for v in o)
        return super().encode(o)

d = {'details': {'hawk_branch': {'tandem': ['4210bnd72']}, 'uclif_branch': {'tandem': ['e2nc712nma89', '23s24212', '12338cm82']}}}
print(json.dumps(d, cls=restore_value))


with open("a.json", "w") as f:
  json.dump(d, f, cls=restore_value)
2个回答

6
原因在于:
如果您查看github上的CPython / Lib / json中的json.__init__.py源代码:https://github.com/python/cpython/blob/master/Lib/json/init.py 您会发现json.dump实际上使用的是:
if (not skipkeys and ensure_ascii and
    check_circular and allow_nan and
    cls is None and indent is None and separators is None and
    default is None and not sort_keys and not kw):
    iterable = _default_encoder.iterencode(obj)
else:
    if cls is None:
        cls = JSONEncoder
    iterable = cls(skipkeys=skipkeys, ensure_ascii=ensure_ascii,
        check_circular=check_circular, allow_nan=allow_nan, indent=indent,
        separators=separators,
        default=default, sort_keys=sort_keys, **kw).iterencode(obj)
# could accelerate with writelines in some versions of Python, at
# a debuggability cost
for chunk in iterable:
    fp.write(chunk)

因此,您想要覆盖的函数应该是json.JSONEncoder.iterencode而不是encode

1
谢谢,这个可以用,但是我失去了所有的缩进。有没有一种方法可以保留缩进? - MaverickD
“缩进”是什么意思?默认情况下,JSON文件不应该有缩进,而应该是单行。 - Rocky Li
这个代码是可以运行的,但是我遇到了一个小问题,就是在调用iterencode函数时传入了一个未知参数(_one_shot)。解决方法是在我的iterencode函数定义中添加**kwargs作为一个参数。 - JimmyJames

3
使用cls参数的json.dumps会调用您的JSON对象上的encode方法,该方法将返回字符串表示形式。另一方面,json.dump将调用您未实现的default方法。根据json.dump文档:

要使用自定义的JSONEncoder子类(例如,覆盖default()方法以序列化其他类型的子类),请使用cls kwarg指定;否则将使用JSONEncoder。

因此,json.dump使用默认的default方法,不会影响您的原始对象并写入它。
编写您想要的文件的最简单方法是:
with open("a.json", "w") as f:
    f.write(json.dumps(d, cls=restore_value))

1
谢谢。这有意义。在这种方式下,同时使用“缩进”是否可能? 我正在失去所有缩进。 - MaverickD
唯一的缺点是,如果OP的字典非常大,则由于“iterencode”作为生成器的工作方式,它能够将自己分成多个部分以避免出现内存问题,因此可能无法正常工作。 - Rocky Li

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接