【问题标题】:Custom formatting for just a portion of data during JSON serialization在 JSON 序列化期间仅为部分数据自定义格式
【发布时间】:2019-06-04 21:55:32
【问题描述】:

我有一个要序列化的 Python 对象树。它的结构是这样的:

{
    "regular_object": {
        ...
    },
    "regular_field": ...
    "special_object": {
        "f1": "v1",
        "f2": v2,
        ...
        "fn": "vn"
    }
}

我想序列化这样的对象树,以便在 JSON 表示中像这样格式化:

{
    "regular_object": {
        ...
    },
    "regular_field": ...
    "special_object": { "f1": "v1", "f2": v2, ..., "fn": "vn" }
}

换句话说,我想要基于被格式化对象的内容的混合格式

我尝试通过覆盖default 方法并使用自定义类对象来存储special_object 的内容来使用json.JSONEncoder,以便调用自定义default,如下所示:

class SpecialObject:
    def __init__(self, attrs: dict):
        self.attrs = attrs

class SpecialEncoder(json.JSONEncoder):
    def default(self, o):
        JE = json.JSONEncoder
        if isinstance(o, SpecialObject):
            return f"{{ ', '.join([f'{JE.encode(self, el[0])}: {JE.encode(el[1])}' for el in o.attrs.items()]) }}"
        else:
            return super().default(o)

但看起来JSONEncoder 再次编码了default 的返回值,因此我为自定义类型生成的字符串被序列化为字符串,输出类似于:

{
    ...
    "special_object": "{ \"f1\": \"v1\", ..., \"fn\": \"vn\" }"
}

这显然不是我的想法。我还尝试像这里的一些答案所建议的那样覆盖JSONEncoder.encode 方法,但是该方法永远不会从JSONEncoder 本身中被调用。

我确实找到了 several approaches 来解决看似相似的问题,但那里提出的解决方案看起来太怪异,而且对于像我这样的任务来说过于复杂。 有没有办法在不深入JSONEncoder 的肠子的情况下,只用一两打代码来实现我所需要的?它不需要是一个通用的解决方案,只需我可以快速修复并且可能仅适用于单个案例。

【问题讨论】:

  • 您想要的输出不是 JSON。
  • @Goyo,怎么样?它介于文档的漂亮打印和紧凑打印示例之间
  • 因为任何 JSON 解析器在解析它时都会失败。我不明白你对这些例子的看法。
  • @Goyo, are you sure?
  • @OlvinR​​oght 那么v2 周围没有引号只是一个忽略,我们只是在这里谈论换行符?

标签: python json python-3.x serialization json-serialization


【解决方案1】:

其实,你可以做这样的事情,但它是 ... 你看它的样子。

def custom_json_dumps(obj, *args, keys_to_skip_indent=(), **kwargs):
    if isinstance(obj, dict):
        indent = kwargs.pop("indent", 0)
        separators = kwargs.get("separators", (", ", ": "))

        res = "{" + "\n" * int(indent != 0)
        for k, v in obj.items():
            if k in keys_to_skip_indent or indent == 0:
                encoded = json.dumps(v, *args, **kwargs)
            else:
                encoded = json.dumps(v, *args, indent=indent, **kwargs)
            res += "\"{}\"".format(k) + separators[1] + encoded + separators[0] + "\n" * int(indent != 0)

        return res[:res.rindex(separators[0])].replace("\n", "\n" + " " * indent) + "\n" * int(indent != 0) + "}"
    else:
        return json.dumps(obj, *args, **kwargs)

测试:

o = {
    "regular_object": {
        "a": "b"
    },
    "regular_field": 100000,
    "float_test": 1.0000001,
    "bool_test": True,
    "list_test": ["1", 0, 1.32, {"a": "b"}],
    "special_object": {
        "f1": "v1",
        "f2": "v2",
        "fn": "vn"
    }
}

print(custom_json_dumps(o, keys_to_skip_indent={"special_object",  "list_test"}, indent=4))

输出:

{
    "regular_object": {
        "a": "b"
    }, 
    "regular_field": 100000, 
    "float_test": 1.0000001, 
    "bool_test": true, 
    "list_test": ["1", 0, 1.32, {"a": "b"}], 
    "special_object": {"f1": "v1", "f2": "v2", "fn": "vn"}
}

【讨论】:

  • 它并不漂亮,基本上是体力劳动,但在没有更好的解决方案的情况下它会做。感谢您的努力 =)
  • @Semisonic,还有另一个“正确”的解决方案,继承 JSONEncoder 并覆盖几个方法,但它的代码很容易增加 10 倍。如果有人有兴趣,如何正确地做到这一点,我可以将其添加到帖子中。
  • 我认为indent = kwargs.pop("indent", 0) 会为您节省几行代码,同时让您的意图更加明显。
  • @OlvinR​​oght,如果您的“正确”解决方案不需要对 JSONEncoder 的内部进行逆向工程,那将会很有趣。但是,从我在这里找到的类似问题的答案来看,这似乎是不可能的。
猜你喜欢
  • 2020-02-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-06-03
  • 2012-01-23
  • 2013-03-05
相关资源
最近更新 更多