首页 > 解决方案 > 我怎样才能让 json.dumps 将我的课程视为字典?

问题描述

我想创建一个自定义 Python 类,它像字典一样进行 JSON 序列化。以Python 的duck-typing 命名,我想我可以创建一个看起来和嘎嘎声完全像字典的类。但是,下面显示的类显然不够像 dict json.dumps- 下面的代码会产生错误TypeError: Object of type TotallyADict is not JSON serializable。我可以对 TotallyADict 进行哪些更改,以便json.dumps输出的默认编码器{"a": 1, "b": 2, "c": 3}

我知道这个直接的问题可以通过创建自定义编码器来解决,但是在这个特定问题已经从中提炼出来的更大问题中,这不是一个可接受的解决方案。

另一个尝试的解决方案是让 TotallyADict 继承自 dict 而不是 MutableMapping。这不会引发任何异常,但在这种情况下会json.dumps(x)产生{}json.dumps显然,用于 dicts的默认编码器的数据源不是以下任何被覆盖的方法。

我在这里想要的是能够使用属性语义(x.c = x.a + x.b)但仍然序列化为 JSON 对象。因此,一个似乎不起作用的可能建议是 TypedDict (必须是x['c'] = x['a'] + x['b'])。__setattr__通过and拦截属性分配和检索并重定向到继承自__getattribute__的条目似乎工作得很好,所以这是我的默认解决方案。但令我惊讶的是,有一次我真的想使用鸭式打字而不是严格(ish)打字,它似乎不起作用。selfdict

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))

标签: pythonjsondictionaryduck-typing

解决方案


这里的问题是你的_fields变量。这不会序列化为 JSON 对象,因为{'c', 'b', 'a'}它不是有效的 json。如果您查看该x.__dict__属性,您可以看到该对象将被表示为什么。

{'a': 1, 'b': 2, 'c': 3, '_fields': {'c', 'b', 'a'}}

如果您更改_fields为列表,您还可以使用defaultJSON.dumps 中的参数

这些是我为使您正在寻找的工作所做的更改

self._fields = ['a', 'b', 'c']
print(json.dumps(x, default=vars))

这是我的 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))

您也可以尝试使用UserDict

https://docs.python.org/3/library/collections.html#collections.UserDict


推荐阅读