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



我正在尝试保存我的QGraphicsScene,其中正在绘制的项目可能包含对场景中其他项目的引用。

这引起了一个主要问题,因为当保存到 json 并从中加载时,对象可能会出现在正确的位置,但引用是我不确定如何实现的。

有没有办法存储引用指向哪个对象?(引用的对象也存储在 JSON 中(

我使用getstatesetstate方法来序列化我的对象,并且那些永远不会有引用但总是被引用的对象首先添加到 json 中,以便在加载时引用对象在那里。

这是一个 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)

从上面的示例中,我想保留 line1 和 line2,以便在加载后引用特定形状。

你可能需要一个JSONEncoder。可以在输入数据通过json库的清理器之前解析输入数据以json.dump的东西。

您可以通过挂钩cls=Typer类来做到这一点,如下所示:

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))

这段代码到目前为止并不完美,我知道它有很多问题。但是,只要您注意循环依赖项,它就可以99%时间内完成工作。

使用上面的代码,您需要做的就是两件事。首先是实现一个json函数,因为JSON_Encoder将寻找elif hasattr(obj, 'json'):(如果您觉得这更合乎逻辑,您可以将其更改为to_json()或其他内容(。

其次,在执行json.dump(items, file)时,只需将其更改为:

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

因为它将为 JSON 结构中看到的所有对象附加预解析器。请注意,它将遍历字典,因此如果您的 JSON 结构很大,可能会感觉有点慢。你需要实现一个逻辑来将你的shape转换为它的表示,这样你就可以json.load解释数据并在 JSON 中替换该位置。

这是我多年来喜欢的最好的方式,它有点笨拙,但我取得了一些成功。

虽然@Torxed回答了 python 类的可序列化性,但我的主要目标是保存对对象的引用。但是由于在加载时我正在创建新对象,因此在 json 文件中存储引用成为一个挑战。

我的方法是使用 hex(id(ref(( 为对象分配 id,并且相同的 ref id 存储在引用对象中。

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']]

在加载时,我只需创建我添加的所有形状的字典,并将其 id 作为键,并在加载时分配新引用。

最新更新