首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >将Python类层次结构的对象序列化为现有的JSON模式

将Python类层次结构的对象序列化为现有的JSON模式
EN

Stack Overflow用户
提问于 2020-07-25 22:44:39
回答 1查看 240关注 0票数 1

问题标题抓住了我想要做的事情。但为了更具体,让我们假设我有这样的模式(这是python-jsonschema:https://python-jsonschema.readthedocs.io/en/stable/中的模式定义):

代码语言:javascript
复制
schema = {
     "type" : "object",
     "properties" : {
         "price" : {"type" : "number"},
         "name" : {"type" : "string"},
     },
 }

这是一个有效的文档:

代码语言:javascript
复制
{"name" : "Eggs", "price" : 34.99}

这两个类:

代码语言:javascript
复制
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。这就是我目前正在做的事情。它看起来像这样:

代码语言:javascript
复制
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()方法散布在我的代码中。

有什么建议吗?

EN

回答 1

Stack Overflow用户

发布于 2020-07-25 22:55:49

您需要为任何重要任务创建JSONEncoder子类,但这个子类还可以在它序列化的对象(如asdict方法)中查找约定,并回退到__dict__

我已经写了一个名为dkjason的模块,你可以看看它的灵感(它也在PyPI上),下面是它的要点(我们称我们的asdict方法为__json__()):

代码语言:javascript
复制
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)

以及一个调用它的方便方法:

代码语言:javascript
复制
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子类中。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/63089696

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档