[英]How can I have json.dumps treat my class as a dict?
I would like to create a custom Python class that JSON-serializes like a dict.我想创建一个自定义 Python class 像字典一样进行 JSON 序列化。 Taking Python's duck-typing at its name, I thought I could create a class that looks and quacks exactly like a dict.
以 Python 的鸭子类型命名,我想我可以创建一个 class,它看起来和嘎嘎声都像一个字典。 However, the class shown below is apparently not dict-like enough for
json.dumps
-- the code below produces the error TypeError: Object of type TotallyADict is not JSON serializable
. However, the class shown below is apparently not dict-like enough for
json.dumps
-- the code below produces the error TypeError: Object of type TotallyADict is not JSON serializable
. What can I change about TotallyADict so that the default encoder for json.dumps
will output {"a": 1, "b": 2, "c": 3}
?我可以对 TotallyADict 进行哪些更改,以便 json.dumps 的默认编码器将
json.dumps
{"a": 1, "b": 2, "c": 3}
?
I know this immediate issue can be resolved by creating a custom encoder, but that is not an acceptable solution in the larger issue this specific problem has been distilled from.我知道这个直接的问题可以通过创建自定义编码器来解决,但是在这个特定问题已经从中提炼出来的更大问题中,这不是一个可接受的解决方案。
Another attempted solution is to have TotallyADict inherit from dict rather than MutableMapping.另一个尝试的解决方案是让 TotallyADict 继承自 dict 而不是 MutableMapping。 This does not throw any exceptions, but in that case
json.dumps(x)
yields {}
;这不会引发任何异常,但在这种情况下
json.dumps(x)
产生{}
; apparently the data source the default encoder for json.dumps
uses for dicts is not any of the overridden methods below.显然,json.dumps 用于
json.dumps
的默认编码器的数据源不是以下任何被覆盖的方法。
What I want here is to able to use attribute semantics ( x.c = xa + xb
) but still serialize into a JSON object.我在这里想要的是能够使用属性语义(
x.c = xa + xb
)但仍然序列化为 JSON object。 So, a possible suggestion that does not seem to work is TypedDict (would have to be x['c'] = x['a'] + x['b']
).因此,一个似乎不起作用的可能建议是 TypedDict (必须是
x['c'] = x['a'] + x['b']
)。 Intercepting attribute assignment and retrievals via __setattr__
and __getattribute__
and redirecting to entries self
which inherits from dict
seems to work well enough, so that's my default solution.通过
__setattr__
和__getattribute__
拦截属性分配和检索并重定向到从dict
继承的条目self
似乎工作得很好,所以这是我的默认解决方案。 But I'm surprised that the one time I actually want to use duck-typing rather than strict(ish) typing, it doesn't seem to work.但令我惊讶的是,有一次我真的想使用鸭式打字而不是严格(ish)打字,它似乎不起作用。
from collections.abc import MutableMapping
import json
class TotallyADict(MutableMapping):
def __init__(self, a, b, c):
self.a = a
self.b = b
self.c = c
self._fields = {'a', 'b', 'c'}
def __getitem__(self, key):
if key in self._fields:
return getattr(self, key)
else:
raise KeyError('"{}" is not a field in {}'.format(key, type(self).__name__))
def __setitem__(self, key, value):
if key in self._fields:
setattr(self, key, value)
else:
raise KeyError('"{}" is not a field in {}'.format(key, type(self).__name__))
def __delitem__(self, key):
raise RuntimeError('Cannot delete fields from {}'.format(type(self).__name__))
def __iter__(self):
return iter(self._fields)
def __len__(self):
return len(self._fields)
def __contains__(self, k):
return k in self._fields
def copy(self):
return type(self)(**{k: getattr(self, k) for k in self._fields})
def __repr__(self):
return '{' + ', '.join('"{}": {}'.format(k, repr(getattr(self, k))) for k in self._fields) + '}'
def get(self, key, default=None):
if key in self._fields:
value = getattr(self, key)
if value is None:
value = default
return value
else:
raise KeyError('"{}" is not a field in {}'.format(key, type(self).__name__))
def setdefault(self, key, default=None):
if key in self._fields:
value = getattr(self, key)
if value is None:
value = default
setattr(self, key, value)
return value
else:
raise KeyError('"{}" is not a field in {}'.format(key, type(self).__name__))
def pop(self, key, value=None):
raise RuntimeError('Cannot delete fields from {}'.format(type(self).__name__))
def keys(self):
return self._fields
def items(self):
return [(k, getattr(self, k)) for k in self._fields]
def values(self):
return [getattr(self, k) for k in self._fields]
def __eq__(self, other):
if type(self) is type(other):
for k in self._fields:
if getattr(self, k) != getattr(other, k):
return False
return True
else:
return False
def __ne__(self, other):
return not self.__eq__(other)
x = TotallyADict(1, 2, 3)
print(json.dumps(x))
In some instances, the easiest solution is the best one.在某些情况下,最简单的解决方案是最好的解决方案。 In this case, create a
to_dict()
function that returns the data inside your custom class as a Python dictionary before json dumping it. In this case, create a
to_dict()
function that returns the data inside your custom class as a Python dictionary before json dumping it.
This way, you can manipulate the data within your class at your leisure, and convert it to a dictionary when other libraries expect a dictionary.这样,您可以在闲暇时操作 class 中的数据,并在其他库需要字典时将其转换为字典。 Then if you need the opposite, just write another function that parses dict into your custom class.
然后,如果您需要相反的内容,只需编写另一个 function 将 dict 解析为您的自定义 class。
Since this class is intended to hold data, I recommend using DataClasses .由于此 class 旨在保存数据,因此我建议使用DataClasses 。
Then you can just add this function to your class to get its attributes as a dict:然后你可以把这个 function 添加到你的 class 来获取它的属性作为一个字典:
from dataclasses import dataclass, asdict
def get_as_dict(self):
return {k: v for k, v in asdict(self).items() if self._dataclass_fields_[k].repr}
The issue here is your _fields
variable.这里的问题是您的
_fields
变量。 This wont serialize to a JSON object as {'c', 'b', 'a'}
is not valid json.这不会序列化为 JSON object,因为
{'c', 'b', 'a'}
无效 json。 If you look at the x.__dict__
property you can see what this object will be represented as.如果您查看
x.__dict__
属性,您可以看到这个 object 将被表示为什么。
{'a': 1, 'b': 2, 'c': 3, '_fields': {'c', 'b', 'a'}}
If you change _fields
to a list you could also use the default
parameter in JSON.dumps如果您将
_fields
更改为列表,您还可以使用 JSON.dumps 中的default
参数
These are the changes I made to get what you are looking for to work这些是我为使您正在寻找的工作所做的更改
self._fields = ['a', 'b', 'c']
print(json.dumps(x, default=vars))
Here is the full code with my canges.这是我的 canges 的完整代码。
from collections.abc import MutableMapping
import json
class TotallyADict(MutableMapping):
def __init__(self, a, b, c):
self.a = a
self.b = b
self.c = c
self._fields = ['a', 'b', 'c']
def __getitem__(self, key):
if key in self._fields:
return getattr(self, key)
else:
raise KeyError('"{}" is not a field in {}'.format(key, type(self).__name__))
def __setitem__(self, key, value):
if key in self._fields:
setattr(self, key, value)
else:
raise KeyError('"{}" is not a field in {}'.format(key, type(self).__name__))
def __delitem__(self, key):
raise RuntimeError('Cannot delete fields from {}'.format(type(self).__name__))
def __iter__(self):
return iter(self._fields)
def __len__(self):
return len(self._fields)
def __contains__(self, k):
return k in self._fields
def copy(self):
return type(self)(**{k: getattr(self, k) for k in self._fields})
def __repr__(self):
return '{' + ', '.join('"{}": {}'.format(k, repr(getattr(self, k))) for k in self._fields) + '}'
def get(self, key, default=None):
if key in self._fields:
value = getattr(self, key)
if value is None:
value = default
return value
else:
raise KeyError('"{}" is not a field in {}'.format(key, type(self).__name__))
def setdefault(self, key, default=None):
if key in self._fields:
value = getattr(self, key)
if value is None:
value = default
setattr(self, key, value)
return value
else:
raise KeyError('"{}" is not a field in {}'.format(key, type(self).__name__))
def pop(self, key, value=None):
raise RuntimeError('Cannot delete fields from {}'.format(type(self).__name__))
def keys(self):
return self._fields
def items(self):
return [(k, getattr(self, k)) for k in self._fields]
def values(self):
return [getattr(self, k) for k in self._fields]
def __eq__(self, other):
if type(self) is type(other):
for k in self._fields:
if getattr(self, k) != getattr(other, k):
return False
return True
else:
return False
def __ne__(self, other):
return not self.__eq__(other)
x = TotallyADict(1, 2, 3)
print(json.dumps(x, default=vars))
You could also try using a UserDict
您也可以尝试使用
UserDict
https://docs.python.org/3/library/collections.html#collections.UserDict https://docs.python.org/3/library/collections.html#collections.UserDict
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.