简体   繁体   English

在 python json 中的对象内部序列化 object 引用

[英]Serializing object references inside objects in python json

I am attempting to saving my QGraphicsScene, where the items being drawn may contain references to other items on the scene.我正在尝试保存我的 QGraphicsScene,其中正在绘制的项目可能包含对场景中其他项目的引用。

This raises a major concern as when saving to json, and loading from it, the object may appear at correct position but the references is something I am unsure of how to implement.这引起了一个主要问题,因为当保存到 json 并从中加载时,object 可能会出现在正确的 position 中,但我不确定如何实现。

Is there a way how I can store which object a reference is to?有没有办法我可以存储引用的 object 是什么? (the referred object is also stored in the json) (引用的object也存储在json中)

I use getstate and setstate methods to serialize my objects, and the ones that will never have references but are always referred are added to the json first so the reference objects are there when loaded.我使用getstatesetstate方法来序列化我的对象,并且那些永远不会有引用但总是被引用的对象首先添加到 json 中,因此引用对象在加载时就在那里。

Here is an MRE:这是一个MRE:

import json
class shape():
    def __init__(self, x, y, width, height):
        self.x = x
        self.y = y
        self.width = width
        self.height = height

    def __getstate__(self):
        return {
            "x": self.x,
            "y": self.y,
            "width": self.width,
            "height": self.height
        }

    def __setstate__(self, dict):
        pass

class line():
    def __init__(self, shape1=None, shape2=None):
        self.shape1 = shape1
        self.shape2 = shape2

    def __getstate__(self):
        return {
            "shape1": self.shape1,
            "shape2": self.shape2
        }

    def __setstate__(self, dict):
        self.shape1 = dict['shape1']
        self.shape2 = dict['shape2']

if __name__ == "__main__":
    shapes = [shape(4, 4 , 4, 4) for _ in range(4)]
    line1 = line(shapes[0], shapes[1])
    line2 = line(shapes[2], shapes[3])
    items = {
        "shapes": [item.__getstate__() for item in shapes],
        "lines": [line1.__getstate__(), line2.__getstate__()] 
    }
    with open("test.json", "w") as file:
        json.dump(items, file)
    with open("test.json", "r") as file:
        item = json.load(file)

From the above example, i want to retain the line1 and line2 to have references to the specific shapes even after loading.从上面的例子中,我想保留 line1 和 line2 以在加载后引用特定的形状。

You probably need a JSONEncoder of sorts.你可能需要一个JSONEncoder之类的。 Something that can parse the input data to json.dump before it gets passed through the sanitizers of the json lib.可以在输入数据通过json.dump库的消毒剂之前将其解析为json的东西。

You can do this by hooking in a cls=Typer class like so:您可以通过挂接cls=Typer class 来做到这一点,如下所示:

from json import JSONEncoder, dumps, loads
from datetime import date, datetime

class JSON_Encoder:
    def _encode(obj):
        if isinstance(obj, dict):
            ## We'll need to iterate not just the value that default() usually gets passed
            ## But also iterate manually over each key: value pair in order to trap the keys.

            for key, val in list(obj.items()):
                if isinstance(val, dict):
                    val = loads(dumps(val, cls=JSON_Typer)) # This, is a EXTREMELY ugly hack..
                                                            # But it's the only quick way I can think of to 
                                                            # trigger a encoding of sub-dictionaries. (I'm also very tired, yolo!)
                else:
                    val = JSON_Encoder._encode(val)
                del(obj[key])
                obj[JSON_Encoder._encode(key)] = val
            return obj
        elif hasattr(obj, 'json'):
            return obj.json()
        elif isinstance(obj, (datetime, date)):
            return obj.isoformat()
        elif isinstance(obj, (list, set, tuple)):
            r = []
            for item in obj:
                r.append(loads(dumps(item, cls=JSON_Typer)))
            return r
        else:
            return obj

class JSON_Typer(JSONEncoder):
    def _encode(self, obj):
        return JSON_Encoder._encode(obj)

    def encode(self, obj):
        return super(JSON_Typer, self).encode(self._encode(obj))

This code is by far not perfect , I'm aware of many issues with it.这段代码到目前为止并不完美,我知道它有很多问题。 But it gets the job done 99% of the time as long as you're careful with circular dependencies.但只要你小心循环依赖,它就能在99%的时间内完成工作。

With the above code, all you need to do is two things.使用上面的代码,您只需要做两件事。 The first being implementing a json function as the JSON_Encoder will be looking for elif hasattr(obj, 'json'): (you could change this to to_json() or something if you feel that's more logical) .第一个实现json function 作为JSON_Encoder将寻找elif hasattr(obj, 'json'): (如果您觉得这更合乎逻辑,您可以将其更改为to_json()或其他内容)

Secondly, when doing json.dump(items, file) , simply change it to:其次,在做json.dump(items, file)时,只需将其更改为:

json.dump(items, file, cls=JSON_Typer)

As it will append the pre-parser for all the objects seen in the JSON structure.因为它将 append 预解析器用于 JSON 结构中看到的所有对象。 Note here that it will traverse the dictionary, so it might feel a bit slow if your JSON structure is massive.请注意,它会遍历字典,因此如果您的 JSON 结构很大,它可能会感觉有点慢。 And you'll need to implement a logic to convert yout shape into a representation of it so you upon json.load can interpret the data and do a replacement of that position in the JSON.而且您需要实现一个逻辑来将您的shape转换为它的表示形式,以便您在json.load上可以解释数据并替换 JSON 中的 position。

This is the best way I've fond over the years, it's a bit hacky but I've had some success with it.这是我多年来最喜欢的方式,它有点老套,但我已经取得了一些成功。

While @Torxed answers serializability of python classes, My main objective was saving references to objects.虽然@Torxed 回答了 python 类的可序列化问题,但我的主要目标是保存对对象的引用。 But since at loading I was creating new objects, storing references in the json file became a challenge.但由于在加载时我正在创建新对象,因此在 json 文件中存储引用成为一项挑战。

My approach was using hex(id(ref)) to assign id to the object, and the same ref id gets stored in the referring object.我的方法是使用 hex(id(ref)) 将 id 分配给 object,并且相同的 ref id 存储在引用的 object 中。

shapeDict = {}
class shape():
    ...

    def __getstate__(self):
        return {
            "x": self.x,
            "y": self.y,
            "width": self.width,
            "height": self.height,
            "id": hex(id(self))
        }

    def __setstate__(self, dict):
        shapeDict[dict['id']] = self

class line():
    ...

    def __getstate__(self):
        return {
            "shape1": hex(id(self.shape1)),
            "shape2": hex(id(self.shape2))
        }

    def __setstate__(self, dict):
        self.shape1 = shapeDict[dict['shape1']]
        self.shape2 = shapeDict[dict['shape2']]

At load time I just create a dict of all shapes that I have added with their id as keys and the assign the new reference at load time.在加载时,我只创建一个包含我添加的所有形状的字典,并将它们的 id 作为键,并在加载时分配新的引用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM