使用simplejson序列化简单类对象的最简单方法?

31 投票
7 回答
43123 浏览
提问于 2025-04-15 19:46

我正在尝试用 JSON(使用 simplejson)来序列化一组 Python 对象,但遇到了一个错误,提示这个对象“无法被 JSON 序列化”。

这个类很简单,里面的字段只有整数、字符串和浮点数,并且从一个父类继承了一些类似的字段,比如:

class ParentClass:
  def __init__(self, foo):
     self.foo = foo

class ChildClass(ParentClass):
  def __init__(self, foo, bar):
     ParentClass.__init__(self, foo)
     self.bar = bar

bar1 = ChildClass(my_foo, my_bar)
bar2 = ChildClass(my_foo, my_bar)
my_list_of_objects = [bar1, bar2]
simplejson.dump(my_list_of_objects, my_filename)

这里的 foo 和 bar 就是我上面提到的简单类型。唯一有点棘手的是,ChildClass 有时会有一个字段指向另一个对象(这个对象的类型既不是 ParentClass 也不是 ChildClass)。

用 simplejson 将这个序列化成 JSON 对象的最简单方法是什么呢?只要把它转成字典就可以了吗?最好的办法是为 ChildClass 写一个 dict 方法吗?最后,那个指向另一个对象的字段会让事情变得复杂吗?如果是这样的话,我可以重写我的代码,只在类中使用简单字段(比如字符串、浮点数等)。

谢谢。

7 个回答

3

根据Python的JSON文档的说明 // help(json.dumps) // >

你只需要重写 JSONEncoderdefault() 方法,就可以提供自定义的数据类型转换,然后把它作为 cls 参数传入。

下面是我用来处理MongoDB特殊数据类型(比如日期时间和ObjectId)的一个例子:

class MongoEncoder(json.JSONEncoder):
    def default(self, v):
        types = {
            'ObjectId': lambda v: str(v),
            'datetime': lambda v: v.isoformat()
        }
        vtype = type(v).__name__
        if vtype in types:
            return types[type(v).__name__](v)
        else:
            return json.JSONEncoder.default(self, v)     

调用它的方法非常简单:

data = json.dumps(data, cls=MongoEncoder)
10

一个自定义类的实例可以通过以下函数转换成JSON格式的字符串:

def json_repr(obj):
  """Represent instance of a class as JSON.
  Arguments:
  obj -- any object
  Return:
  String that reprent JSON-encoded object.
  """
  def serialize(obj):
    """Recursively walk object's hierarchy."""
    if isinstance(obj, (bool, int, long, float, basestring)):
      return obj
    elif isinstance(obj, dict):
      obj = obj.copy()
      for key in obj:
        obj[key] = serialize(obj[key])
      return obj
    elif isinstance(obj, list):
      return [serialize(item) for item in obj]
    elif isinstance(obj, tuple):
      return tuple(serialize([item for item in obj]))
    elif hasattr(obj, '__dict__'):
      return serialize(obj.__dict__)
    else:
      return repr(obj) # Don't know how to handle, convert to string
  return json.dumps(serialize(obj))

这个函数可以生成JSON格式的字符串,适用于:

  • 一个自定义类的实例,

  • 一个字典,其中包含自定义类的实例作为叶子节点,

  • 一个自定义类实例的列表。

30

我以前用过这个方法,效果不错:把你自定义的对象编码成JSON对象字面量(就像Python里的dict),结构如下:

{ '__ClassName__': { ... } }

这基本上是一个只有一个项目的dict,它的唯一键是一个特殊的字符串,用来说明编码的是什么类型的对象,而它的值是一个包含实例属性的dict。希望这样说能让你明白。

下面是一个非常简单的编码器和解码器的实现(比我实际用的代码要简单一些):

TYPES = { 'ParentClass': ParentClass,
          'ChildClass': ChildClass }


class CustomTypeEncoder(json.JSONEncoder):
    """A custom JSONEncoder class that knows how to encode core custom
    objects.

    Custom objects are encoded as JSON object literals (ie, dicts) with
    one key, '__TypeName__' where 'TypeName' is the actual name of the
    type to which the object belongs.  That single key maps to another
    object literal which is just the __dict__ of the object encoded."""

    def default(self, obj):
        if isinstance(obj, TYPES.values()):
            key = '__%s__' % obj.__class__.__name__
            return { key: obj.__dict__ }
        return json.JSONEncoder.default(self, obj)


def CustomTypeDecoder(dct):
    if len(dct) == 1:
        type_name, value = dct.items()[0]
        type_name = type_name.strip('_')
        if type_name in TYPES:
            return TYPES[type_name].from_dict(value)
    return dct

在这个实现中,假设你要编码的对象会有一个from_dict()类方法,这个方法知道如何从解码后的dict中重建一个实例。

你可以很容易地扩展编码器和解码器,以支持自定义类型(比如datetime对象)。

编辑,回应你的编辑:像这样的实现的好处是,它会自动编码和解码在TYPES映射中找到的任何对象的实例。这意味着它会自动处理像ChildClass这样的类:

class ChildClass(object):
    def __init__(self):
        self.foo = 'foo'
        self.bar = 1.1
        self.parent = ParentClass(1)

这样应该会生成类似于以下的JSON:

{ '__ChildClass__': {
    'bar': 1.1,
    'foo': 'foo',
    'parent': {
        '__ParentClass__': {
            'foo': 1}
        }
    }
}

撰写回答