Xop*_*ter 16 python json subclass namedtuple python-3.6
我试图子类化json.JSONEncoder这样的命名元组(使用新的Python 3.6+语法定义,但它可能仍适用于输出collections.namedtuple)被序列化为JSON对象,其中元组字段对应于对象键.
例如:
from typing import NamedTuple
class MyModel(NamedTuple):
foo:int
bar:str = "Hello, World!"
a = MyModel(123) # Expected JSON: {"foo": 123, "bar": "Hello, World!"}
b = MyModel(456, "xyzzy") # Expected JSON: {"foo": 456, "bar": "xyzzy"}
Run Code Online (Sandbox Code Playgroud)
我的理解是我子类化json.JSONEncoder并覆盖它的default方法来为新类型提供序列化.然后,课程的其余部分将就递归等方面做正确的事情.因此我想出了以下内容:
class MyJSONEncoder(json.JSONEncoder):
def default(self, o):
to_encode = None
if isinstance(o, tuple) and hasattr(o, "_asdict"):
# Dictionary representation of a named tuple
to_encode = o._asdict()
if isinstance(o, datetime):
# String representation of a datetime
to_encode = o.strftime("%Y-%m-%dT%H:%M:%S")
# Why not super().default(to_encode or o)??
return to_encode or o
Run Code Online (Sandbox Code Playgroud)
当它尝试序列化(即作为cls参数json.dumps)一个datetime值 - 至少部分地证明我的假设 - 时,这种方法有效 - 但是命名元组的检查永远不会被命中,并且它默认将它序列化为一个元组(即,一个JSON数组).奇怪的是,我假设我应该default在我的转换对象上调用超类' 方法,但是当它尝试序列化时会引发异常datetime:"TypeError:'str'类型的对象不是JSON可序列化的",坦率地说没有感!
如果我使命名的元组类型检查更具体(例如,isinstance(o, MyModel)),我会得到相同的行为.但是,我确实发现,如果我也通过将指定的元组检查移到那里来覆盖方法,我几乎可以获得我正在寻找的行为encode:
class AlmostWorkingJSONEncoder(json.JSONEncoder):
def default(self, o):
to_encode = None
if isinstance(o, datetime):
# String representation of a datetime
to_encode = o.strftime("%Y-%m-%dT%H:%M:%S")
return to_encode or o
def encode(self, o):
to_encode = None
if isinstance(o, tuple) and hasattr(o, "_asdict"):
# Dictionary representation of a named tuple
to_encode = o._asdict()
# Here we *do* need to call the superclass' encode method??
return super().encode(to_encode or o)
Run Code Online (Sandbox Code Playgroud)
这是有效的,但不是递归的:它根据我的要求成功地将顶级命名元组序列化为JSON对象,但是在该命名元组中存在的任何命名元组都将使用默认行为(JSON数组)进行序列化.如果我在default 和 encode方法中都使用了命名的元组类型检查,这也是行为.
该文档暗示只default应在子类中更改该方法.我想,例如,覆盖encode在AlmostWorkingJSONEncoder会使其破裂时,它在做分块编码.然而,到目前为止,没有多少hackery产生我想要的东西(或者期望发生,因为文档很少).
我的误会在哪里?
编辑阅读代码json.JSONEncoder解释了为什么该default方法在传递字符串时引发类型错误:文档中不清楚(至少对我而言),但该default方法旨在将某些不受支持的类型的值转换为可序列化类型,然后返回; 如果不支持的类型不转化为你的重写方法什么,那么你应该调用super().default(o)在最后调用类型的错误.所以像这样:
class SubJSONEncoder(json.JSONEncoder):
def default(self, o):
if isinstance(o, Foo):
return SerialisableFoo(o)
if isinstance(o, Bar):
return SerialisableBar(o)
# etc., etc.
# No more serialisation options available, so raise a type error
super().default(o)
Run Code Online (Sandbox Code Playgroud)
我相信我遇到的问题是,default只有当编码器无法匹配任何支持的类型时,才会调用该方法.一个命名元组仍然是一个元组 - 它是受支持的 - 所以它在委托给我的重写default方法之前首先匹配.在Python 2.7中,执行此匹配的函数是JSONEncoder对象的一部分,但在Python 3中,它们似乎已移到模块命名空间外(因此,用户空间无法访问).因此,我认为不可能以JSONEncoder通用的方式将子类序列化为命名元组,而无需对您自己的实现进行大量重写和硬耦合:(
编辑2我提交了这个错误.
嗯,我刚刚查看了源代码,似乎没有公共挂钩来控制列表或元组实例的序列化方式。
一种不安全的方法是对_make_iterencode()私有函数进行猴子修补。
另一种方法是预处理输入,将命名元组转换为字典:
from json import JSONEncoder
from typing import NamedTuple
from datetime import datetime
def preprocess(tree):
if isinstance(tree, dict):
return {k: preprocess(v) for k, v in tree.items()}
if isinstance(tree, tuple) and hasattr(tree, '_asdict'):
return preprocess(tree._asdict())
if isinstance(tree, (list, tuple)):
return list(map(preprocess, tree))
return tree
class MD(JSONEncoder):
def default(self, o):
if isinstance(o, datetime):
return o.strftime("%Y-%m-%dT%H:%M:%S")
return super().default(o)
Run Code Online (Sandbox Code Playgroud)
应用于这些模型:
class MyModel(NamedTuple):
foo: int
bar: str = "Hello, World!"
class LayeredModel(NamedTuple):
baz: MyModel
fob: list
a = MyModel(123)
b = MyModel(456, "xyzzy")
c = LayeredModel(a, [a, b])
outer = dict(a=a, b=b, c=c, d=datetime.now(), e=10)
print(MD().encode(preprocess(outer)))
Run Code Online (Sandbox Code Playgroud)
给出这个输出:
{"a": {"foo": 123, "bar": "Hello, World!"},
"b": {"foo": 456, "bar": "xyzzy"},
"c": {"baz": {"foo": 123, "bar": "Hello, World!"},
"fob": [{"foo": 123, "bar": "Hello, World!"},
{"foo": 456, "bar": "xyzzy"}]},
"d": "2019-11-03T10:46:17",
"e": 10}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
2067 次 |
| 最近记录: |