- c - 在位数组中找到第一个零
- linux - Unix 显示有关匹配两种模式之一的文件的信息
- 正则表达式替换多个文件
- linux - 隐藏来自 xtrace 的命令
我正在尝试子类化 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"}
我的理解是我将 json.JSONEncoder
子类化并重写它的 default
方法来为新类型提供序列化。然后类(class)的其他人将在递归等方面做正确的事情。因此我想出了以下内容:
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
这在它尝试序列化(即,作为 json.dumps
的 cls
参数)datetime
值时起作用——到至少部分地证明了我的假设——但从未命中命名元组的检查,并且它默认将其序列化为元组(即,JSON 数组)。奇怪的是,我假设我应该在转换后的对象上调用父类(super class)的 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)
这有效,但不是递归的:它成功地将顶级命名元组序列化为 JSON 对象,根据我的要求,但是该命名元组中存在的任何命名元组都将使用默认行为(JSON 数组)序列化。如果我将命名元组类型检查放在 default
和 encode
方法中,这也是行为。
文档暗示只有 default
方法应该在子类中被改变。例如,我假设在 AlmostWorkingJSONEncoder
中覆盖 encode
会导致它在进行分 block 编码时中断。然而,到目前为止,没有多少 hackery 产生了我想要的东西(或者预期会发生,因为文档很少)。
我的误解在哪里?
EDIT 阅读 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)
我认为我遇到的问题是 default
方法仅在无法匹配任何支持的类型时由编码器调用。命名元组仍然是一个元组——是支持的——所以它在委托(delegate)给我重写的 default
方法之前首先匹配它。在 Python 2.7 中,执行此匹配的函数是 JSONEncoder
对象的一部分,但在 Python 3 中,它们似乎已移到模块命名空间之外(因此,用户空间无法访问) .因此,我认为如果不对您自己的实现进行大量重写和硬耦合,就不可能将 JSONEncoder
子类化以以通用方式序列化命名元组:(
编辑 2 我将其作为 bug 提交。
最佳答案
嗯,我刚刚查看了 the source,似乎没有公共(public) Hook 来控制列表或元组实例的序列化方式。
一种不安全的方法是猴子修补 _make_iterencode() 私有(private)函数。
另一种方法是预处理输入,将命名元组转换为字典:
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)
应用于这些模型:
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)))
给出这个输出:
{"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}
关于python - 了解 JSONEncoder 的子类化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43913256/
您好,我有多个 JSON 数据包,就像我在下面写的那样。 { "data" : { "lng" : 36.159999999999997, "lat" : 50.359999999
我需要一些有关扩展标准的帮助json.JSONEncoder在Python中。 我有这样的对象: temp = { "a": "test/string", "b": {
有没有办法配置 JSONEncoder 在将 Double(整数)转换为 JSON 时添加尾随小数点和零? 下面的代码说明了这个问题。 import Foundation struct NumberT
我试图将结构数组保存到 UserDefaults 中,但我无法弄清楚为什么 JsonEncoder 会返回空数据。我有符合 Codable 协议(protocol)的设置模型 struct MenuI
我正在尝试通过重写 default 方法,使用另一种模式使用 json.JSONEncoder 对 JSON 进行编码: class X(json.JSONEncoder): def defa
我正在为具有可能关联值的 enum 类型实现 Codable。由于这些对于每种情况都是唯一的,我认为我可以在编码期间不使用 key 输出它们,然后在解码时简单地查看我能得到什么以恢复正确的情况。 这是
我正在运行 Python 2.7,我正在尝试创建 JSONEncoder 的自定义 FloatEncoder 子类。我遵循了许多示例,例如 this,但似乎都不起作用。这是我的 FloatEncode
与所有 IEEE 7540 系统一样,Swift 中的数字如 4.7被视为类似 4.7000000000000002 的值.所以这并不奇怪: % swift Welcome to Apple Swif
我想知道函数 jsonEncode()来自 dart:convert图书馆是完全可以预测的。也就是说,将jsonEncode()总是对 Map 的键和值进行编码对象以相同的顺序? 最佳答案 答案是:视
我尝试使用 JSONEncoder 将四个不同的数组编码为 json 格式,但无法转换数组。所以,请帮帮我。我在 iOS 11 上使用 swift 4、xcode 9。 我的数组是: var time
这个问题在这里已经有了答案: Swift String escaping when serializing to JSON using Codable (5 个答案) 已关闭 3 年前。
例如我只想将这个示例数组转换成一个 JSON 对象 var test = [String : Any] () test["title"] = "title" test["description"] =
如何将通用结构发送到返回 JSON 的函数? 我正在尝试创建一个函数,它获取一个结构作为参数并返回一个 JSON 数据。我之所以这样做是因为我想避免重复,它将在多个地方和不同的结构中使用(即:用户、客
我有一个包含多个方法和属性的父类: class Animal { var var1: ... var var2: ... func func1() {} func func2(
想使用 JSONEncoder+Encodable 将对象编码成自定义结构。 struct Foo: Encodable { var name: String? var bars: [Ba
我遇到了以下错误 我不知道为什么会出现这个问题,我该如何解决?请帮忙! 注意:我使用的是 Xcode 版本 9.3.1 和 Swift4,我曾尝试使用 JSONCodable.JSONEncoder
我正在实现我自己的 JSONEncoder 来完全按照我的需要处理不同的类。不幸的是,我的自定义编码器返回格式有点错误的字符串。它们被引号包围,某些字符(引号)被转义。 请使用以下代码重现该行为: i
我无法将 collections.namedtuple 转储为正确的 JSON。 首先,考虑 official使用自定义 JSON 序列化程序的示例: import json class Comple
我在 Python 2.7.10 中工作,我有一些二进制数据: binary_data = b'\x01\x03\x00\x00 \xe6\x10\x00\x00\x01\x00\x00\x00\x0
var test = [String : String] () test["title"] = "title" test["description"] = "description" let enco
我是一名优秀的程序员,十分优秀!