问题标题抓住了我想要做的事情。但为了更具体,让我们假设我有这样的模式(这是python-jsonschema:https://python-jsonschema.readthedocs.io/en/stable/中的模式定义):
schema = {
"type" : "object",
"properties" : {
"price" : {"type" : "number"},
"name" : {"type" : "string"},
},
}这是一个有效的文档:
{"name" : "Eggs", "price" : 34.99}这两个类:
class Price:
def __init__(self, price: float):
self.price = price
class Object:
def __init__(self, name: str, price: float):
self.name = name
self.price = Price(price)下面我列出了常见的解决方案以及我对它们的保留意见。因此,我的问题是,是否有另一种我不知道的方法,或者我对以下任何解决方案的保留是错误的。或者,简单地说,什么是最佳实践?
方法1:使用对象的__dict__表示,然后使用json.dumps()进行序列化(参见this answer)。
保留:这会将对象耦合到模式。例如,我现在必须使用模式所需的属性名来命名对象属性。这很糟糕的原因是显而易见的:向后兼容性问题,与编码风格指南的冲突,设计锁定等。还有一条评论是关于链接答案的,有47张赞成票,认为“使用__dict__并不是在所有情况下都有效。如果在对象实例化后没有设置属性,__dict__可能不会完全填充。”我承认我不明白。但这听起来很糟糕。
方法2:子类JSONEncoder (也在this answer中)
保留:这在组织上似乎很有帮助,但它仍然回避了一个问题,即如何在不调用__dict__的情况下实现默认方法,并具有与上面相同的问题。
方法3:在每个类上编写一个自定义asdict。这就是我目前正在做的事情。它看起来像这样:
class Price:
def __init__(self, price: float):
self.price = price
def asdict(self):
# if we had:
# return {"price": self.price}
# Then the nesting mismatch between the class hierachy and the schema would cause a problem.
return self.price
class Object:
def __init__(self, name: str, price: float):
self.name = name
self.price = Price(price)
def asdict(self):
return {"name": self.name, "price": self.price.asdict()}保留:最明显的问题是,我的类层次结构与模式的嵌套结构耦合在一起。您可以在上面看到这导致的问题。更严重的是,这意味着我的序列化定义分布在多个不同类中的多个asdict()方法上。我想要的是有一个名为"serializers.py“的文件,它完整地指定了将我的类层次结构转换为JSON的过程。不要将asdict()方法散布在我的代码中。
有什么建议吗?
发布于 2020-07-25 22:55:49
您需要为任何重要任务创建JSONEncoder子类,但这个子类还可以在它序列化的对象(如asdict方法)中查找约定,并回退到__dict__。
我已经写了一个名为dkjason的模块,你可以看看它的灵感(它也在PyPI上),下面是它的要点(我们称我们的asdict方法为__json__()):
class DkJSONEncoder(json.JSONEncoder):
"""Handle special cases, like Decimal...
"""
def default(self, obj): # pylint:disable=R0911
if isinstance(obj, decimal.Decimal):
return float(obj)
if hasattr(obj, '__json__'):
return obj.__json__()
if isinstance(obj, set):
return list(obj)
if isinstance(obj, ttcal.Year):
return dict(year=obj.year, kind='YEAR')
if isinstance(obj, ttcal.Duration):
return '@duration:%d' % obj.toint()
if isinstance(obj, datetime.datetime):
return '@datetime:%s' % obj.isoformat()
if isinstance(obj, datetime.date):
return '@date:%s' % obj.isoformat()
if isinstance(obj, datetime.time):
return dict(hour=obj.hour,
minute=obj.minute,
second=obj.second,
microsecond=obj.microsecond,
kind="TIME")
if isinstance(obj, QuerySet):
return list(obj)
if hasattr(obj, '__dict__'):
return dict((k, v) for k, v in obj.__dict__.items()
if not k.startswith('_'))
return super(DkJSONEncoder, self).default(obj)以及一个调用它的方便方法:
def dumps(val, indent=4, sort_keys=True, cls=DkJSONEncoder):
"""Dump json value, using our special encoder class.
"""
return json.dumps(val, indent=indent, sort_keys=sort_keys, cls=cls)我建议不要将所有序列化程序都放在一个文件中,因为这会阻止任何不能更改源代码的人对您的方案进行扩展。正如您在上面看到的,您可以根据需要放入或集中到JSONEncoder子类中。
https://stackoverflow.com/questions/63089696
复制相似问题