【问题标题】:Python JSON serialize excluding certain fieldsPython JSON序列化不包括某些字段
【发布时间】:2020-03-24 22:55:24
【问题描述】:

总结

我有一个 Python 对象层次结构,我想使用 JSON 序列化(仅通过 https://docs.python.org/3/library/json.html,不使用任何额外的第三方库)。我想排除某些字段/属性/子对象。我发现很难找到一个关于如何实现这一目标的简单答案?

示例

我将有一个派生类实例,结果如下:

class MyItemClass(BaseItemClass):
    self.saveThisProperty = 999
    self.dontSaveThisProperty = "Something"
    self.saveThisObject = ObjectType1()
    self.dontSaveThisObject = ObjectType2()

如果我要序列化为 XML,我希望它看起来像

<MyItemClass>
    <saveThisProperty>999</saveThisProperty>
    <saveThisObject>
        ...
    </saveThisObject>
</MyItemClass>

请注意,我只序列化某些属性/子对象,我确实想要序列化我的类实例派生自的整个BaseItemClass

在 XML 中我很好。我知道如何在执行我想要的操作时输出 XML 位,或者输出到最后保存的临时内存文档,或者通过增量方式将单个节点/元素输出到流中。我不必序列化所有内容。例如

xmlStream.writeStartElement("MyItemClass")
    xmlStream.writeElementWithValue("saveThisProperty", 999)
    xmlStream.writeStartElement("saveThisObject")
        ...
    xmlStream.writeEndElement("saveThisObject")
xmlStream.writeEndElement("MyItemClass")

对于 JSON,我不能这样做,可以吗?我是否必须通过仅将我想要的属性/子对象复制到其中然后 JSON 序列化来创建一些新的“独立”对象层次结构(没有来自 BaseClass 的派生)? p>

我确实看到有json.dump(default = ...),但上面写着:

如果指定,默认应该是一个函数,该函数会为无法序列化的对象调用。它应该返回对象的 JSON 可编码版本

然而,并不是原始对象不能默认被Python->JSON序列化,而是我不想要那种默认的序列化一切行为,我想要我的“选择性”。

【问题讨论】:

  • 我认为你对json文档的理解有误:用户定义的不能默认被序列化;你需要为default 提供一个可以序列化它们的实现,并且该实现可以做任何你想做的事情,只要结果可以被序列化。
  • 谢谢。您如何为此编写一个 default 函数?我的层次结构将很深,并且涉及不同级别的许多对象类。顶级dump() 调用将没有可用的函数/类,它可能知道它将遇到的类,编码需要在遇到时委托给每个类...?
  • 您可以给每个类一个serialise 方法来产生所需的输出,并让default 在它遇到的每个对象上调用obj.serialise(),或者使用像在答案,或棉花糖等。
  • @snakecharmerb 我们快到了!这就是我目前正在做的事情。问题是:我是(a)通过调用serialise() 的树来构建一个新的“影子,可序列化”单对象树,然后在最后传递给json.dump(),还是(b)每个对象的serialise() 方法不返回任何内容,而是将序列化写入流,在这种情况下如何?
  • 每个对象的serialise 方法应该返回一个dictjson.dumps 将访问对象图中的所有对象并尝试序列化它们,如果它不知道如何序列化对象,则调用 default。你可以写 default 以便调用 serialise 是一个包罗万象的,即没有 isinstance 检查

标签: python json serialization


【解决方案1】:

我是 OP。为了清楚起见,我在这里发布了我最终用于我的案例的内容。

我已将@Sina Rezaei 在此线程中的帖子标记为已接受的解决方案,因为那(他帖子的最后一部分)和@snakechamerb 的 cmets 启发了我了解所需的内容。

我的解决方案的轮廓如下:

class ModelScene(QGraphicsScene):

  # Serialize whole scene to JSON into stream
  def json_serialize(self, stream) -> None:
    # Get `json.dump()` to call `ModelScene.json_serialize_dump_obj()` on every object to be serialized
    json.dump(self, stream, indent=4, default=ModelScene.json_serialize_dump_obj)

  # Static method to be called from `json.dump(default=ModelScene.json_serialize_dump_obj)`
  # This method is called on every object to be dumped/serialized
  @staticmethod
  def json_serialize_dump_obj(obj):
    # if object has a `json_dump_obj()` method call that...
    if hasattr(obj, "json_dump_obj"):
      return obj.json_dump_obj()
    # ...else just allow the default JSON serialization
    return obj

  # Return dict object suitable for serialization via JSON.dump()
  # This one is in `ModelScene(QGraphicsScene)` class
  def json_dump_obj(self) -> dict:
    return {
      "_classname_": self.__class__.__name__,
      "node_data": self.node_data
      }

class CanvasModelData(QAbstractListModel):

  # Return dict object suitable for serialization via JSON.dump()
  # This one is class CanvasModelData(QAbstractListModel)
  def json_dump_obj(self) -> dict:
    _data = {}
    for key, value in self._data.items():
      _data[key] = value
    return {
      "_classname_": self.__class__.__name__,
      "data_type": self.data_type,
      "_data": _data
      }
  • 每个“复杂”类都定义了一个def json_dump_obj(self) -&gt; dict: 方法。
  • 该方法仅返回序列化中所需的属性/子对象。
  • 顶级json.dump(self, stream, default=ModelScene.json_serialize_dump_obj) 通过静态方法ModelScene.json_serialize_dump_obj 使访问的每个节点都被增量序列化为流式传输。如果可用,则调用我的obj.json_dump_obj(),否则默认为基本对象类型的 JSON 序列化。

有趣的是,我遇到了一个和我有同样担忧的人。来自python中json.dump()和json.dumps()有什么区别?,解决方案https://stackoverflow.com/a/57087055/489865

在内存使用和速度方面。

当您调用 jsonstr = json.dumps(mydata) 时,它首先会创建一个完整的 复制内存中的数据,然后你file.write(jsonstr)它 到磁盘。所以这是一种更快的方法,但如果你有一个问题 要保存的大量数据。

当你调用json.dump(mydata, file)——没有's'时,新内存是 未使用,因为数据是按块转储的。但整个过程是 大约慢 2 倍。

来源:我检查了json.dump()json.dumps()的源代码和 还测试了使用time.time() 和 在 htop 中查看内存使用情况。

【讨论】:

    【解决方案2】:

    我可以为您的情况想出三个解决方案:

    解决方案 1: 使用Pykson第三方库,定义你想要序列化的字段为pykson字段。

    示例:

    class MyItemClass(pykson.JsonObject):
        saved_property = pykson.IntegerField()
    
    my_object = MyItemClass(saved_property=1, accept_unknown=True)
    my_object.unsaved_property = 2
    pykson.Pykson().to_json(my_object)
    

    免责声明:我是 pykson 库的开发者。

    解决方案 2: 第二种解决方案是使用带有自定义默认解串器的包装类。

    class ObjectWrapper:
        def __init__(self, value, should_serialize=False)
            self.value = value
            self.should_serialize = should_serialize
    
    def default_handler(obj):
        if isinstance(obj, ObjectWrapper):
            if obj.should_serialize:
                return obj.value
            else:
                return None
        else:
            raise TypeError
    
    json.dump(default=default_handler)
    

    解决方案 3: 这可能是个坏主意,但如果您有一个深层层次结构,您还可以向 allc 类添加一个函数,该函数将被序列化并使用此函数获取字典并轻松将字典转换为 json。

    class MyChildClass:
         def __init__(self, serialized_property, not_serialized_property):
            self.serialized_property = serialized_property
            self.not_serialized_property = not_serialized_property
    
         def to_dict(self):
            # only add serialized property here
            return {
                "serialized_property": self.serialized_property
            }
    
    class MyParentClass:
        def __init__(self, child_property, some_other_property):
            self.child_property = child_property
            self.some_other_property = some_other_property
    
        def to_dict(self):
            return {
                'child_property': self.child_property.to_dict(),
                'some_other_property': self.some_other_property
            }
    
    my_child_object = MyChildClass(serialized_property=1, not_serialized_property=2)
    my_parent_object = MyParentClass(child_property=my_child_object, some_other_property='some string here')
    json.dumps(my_parent_object.to_dict())
    

    或者您可以使用默认处理程序获得相同的结果:

    class MyChildClass:
         def __init__(self, serialized_property, not_serialized_property):
            self.serialized_property = serialized_property
            self.not_serialized_property = not_serialized_property
    
         def to_dict(self):
            # only add serialized property here
            return {
                "serialized_property": self.serialized_property
            }
    
    class MyParentClass:
        def __init__(self, child_property, some_other_property):
            self.child_property = child_property
            self.some_other_property = some_other_property
    
        def to_dict(self):
            return {
                'child_property': self.child_property,
                'some_other_property': self.some_other_property
            }
    
    def handle_default(obj):
        if isinstance(obj, MyChildClass):
            return obj.to_dict()
        elif isinstance(obj, MyParentClass):
            return obj.to_dict()
        return None
    
    my_child_object = MyChildClass(serialized_property=1, not_serialized_property=2)
    my_parent_object = MyParentClass(child_property=my_child_object, some_other_property='some string here')
    json.dumps(my_parent_object, default=handle_default)
    

    【讨论】:

    • 谢谢。恐怕我不想使用任何人的图书馆,所以需要解决方案#2。我不明白你打算如何使用它。在dump(default=function)dump(cls=alternative_JSONEncoder) 上苦苦挣扎。我的层次结构将很深,并且涉及不同级别的许多对象类。顶级dump() 调用将没有可用的函数/类,它可能知道它将遇到的类,编码需要在遇到时委托给每个类...?
    • @JonBrave 检查我的编辑,如果您的预定义类类型数量有限,可能会有所帮助。
    • 还有另一种解决方案可以为未序列化的变量使用特定名称并跳过这些名称。检查这个:stackoverflow.com/a/55151894/5528269
    • 好的,这就像我现在正在做的。您的to_dict() 首先在传递中被调用以构建一个“影子,可序列化”对象层次结构,然后最后一次调用该层次结构的json.dump()。它不会在执行过程中写入序列化流。我不能在一个模块中有一些单一的handle_default 方法,因为它甚至不知道为了编写isinstance(obj, SomeClass) 会遇到哪些类,这些类将在各个模块中到处定义。我正在使用if hasattr(obj, "to_dict"): return obj.to_dict() 使其通用。
    猜你喜欢
    • 2010-11-19
    • 2012-06-07
    • 1970-01-01
    • 1970-01-01
    • 2021-12-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多