简体   繁体   English

如何让 json.dumps 将我的 class 视为字典?

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM