使用常规编码器使对象JSON可序列化

JSON序列化自定义不可序列化对象的常规方法是json.JSONEncoder ,然后将自定义编码器传递给转储。

它通常是这样的:

 class CustomEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, foo): return obj.to_json() return json.JSONEncoder.default(self, obj) print json.dumps(obj, cls = CustomEncoder) 

我想要做的是使用默认的编码器可序列化的东西。 我环顾四周,却找不到任何东西。 我的想法是,编码器会查看一些字段来确定json编码。 类似于__str__ 。 也许是一个__json__字段。 在Python中有这样的东西吗?

我想使我所做的一个模块类成为JSON可序列化给使用该包的每个人,而不用担心自己实现自己的[自定义]自定义编码器。

正如我在对你的问题的评论中所说的,在查看了json模块的源代码之后,它似乎不适合做你想做的事情。 然而,这个目标可以通过所谓的猴子补丁来实现(参见问题什么是猴子补丁? )。 这可以在你的包的__init__.py初始化脚本中完成,并且会影响所有后续的json模块序列化,因为模块通常只加载一次,结果caching在sys.modules

该修补程序更改默认的json编码器的default方法 – 默认的default()

为简单起见,下面是一个作为独立模块实现的例子:

模块: make_json_serializable.py

 """ Module that monkey-patches json module when it's imported so JSONEncoder.default() automatically checks for a special "to_json()" method and uses it to encode the object if found. """ from json import JSONEncoder def _default(self, obj): return getattr(obj.__class__, "to_json", _default.default)(obj) _default.default = JSONEncoder().default # Save unmodified default. JSONEncoder.default = _default # replacement 

使用它是微不足道的,因为通过简单地导入模块来应用这个补丁。

示例客户端脚本:

 import json import make_json_serializable # apply monkey-patch class Foo(object): def __init__(self, name): self.name = name def to_json(self): # New special method. """ Convert to JSON format string representation. """ return "{u'name': %r}" % self.name.decode('utf-8') foo = Foo('sazpaz') print(json.dumps(foo)) # "{u'name': u'sazpaz'}" 

为了保留对象types信息,特殊方法还可以在返回的string中包含它:

  return ("{u'type': %r, u'name': %r}" % (self.__class__.__name__, self.name.decode('utf-8')) 

甚至比replacedefault()寻找一个特殊命名的方法更好,因为它可以自动序列化大多数Python对象,包括用户定义的类实例,而不需要添加特殊的方法。 在研究了一些替代品之后,使用pickle模块的以下内容对我来说似乎最接近理想:

模块: make_json_serializable2.py

 """ Module that imports the json module and monkey-patches it so JSONEncoder.default() automatically pickles any Python objects encountered that aren't standard JSON data types. """ from json import JSONEncoder import pickle def _default(self, obj): return {'_python_object': pickle.dumps(obj)} JSONEncoder.default = _default # Replace with the above. 

当然,一切都不能被腌制,例如扩展types。 然而,有些方法是通过编写特殊的方法,通过pickle协议来处理它们的,类似于你之前提到的和我之前描述的方法,但是这样做对于less得多的情况可能是必要的。

无论如何,使用pickle协议还意味着通过在传入的字典中寻找'_python_object'键的任何json.loads()调用提供自定义的object_hook函数参数来重build原始Python对象将是相当容易的。 :

 def as_python_object(dct): if '_python_object' in dct: return pickle.loads(str(dct['_python_object'])) return dct pyobj = json.loads(json_str, object_hook=as_python_object) 

如果这必须在许多地方完成,那么定义一个自动提供额外关键字参数的包装器函数可能是值得的:

 json_pkloads = functools.partial(json.loads, object_hook=as_python_object) pyobj = json_pkloads(json_str) 

当然,这可能是猴子补丁到json模块,使该函数默认object_hook (而不是None )。

我从Raymond Hettinger的回答中得到了使用pickle到另一个JSON序列化问题的想法,我认为这个问题是非常可信的,也是一个官方的源代码(就像在Python核心开发者中一样)。

请注意,这种确切的方法不能像Python 3中所示的那样工作,因为json.dumps()返回JSONEncoder无法处理的bytes对象。 一个简单的解决方法是latin1 “解码”从pickle.dumps()返回的值,然后在将它传递给as_python_object()函数中的as_python_object()之前,从latin1 “编码”它。 这是有效的,因为任意的二进制string都是有效的latin1 ,它总是可以解码为Unicode,然后再次编码回原始string(正如Sven Marnach在这个答案中指出的那样)。

(尽pipe在Python 2中以下工作正常,但它所做的latin1解码和编码是多余的。)

 from decimal import Decimal class PythonObjectEncoder(json.JSONEncoder): def default(self, obj): return {'_python_object': pickle.dumps(obj).decode('latin1')} def as_python_object(dct): if '_python_object' in dct: return pickle.loads(dct['_python_object'].encode('latin1')) return dct data = [1,2,3, set(['knights', 'who', 'say', 'ni']), {'key':'value'}, Decimal('3.14')] j = json.dumps(data, cls=PythonObjectEncoder, indent=4) data2 = json.loads(j, object_hook=as_python_object) assert data == data2 # both should be same 

您可以像这样扩展字典类:

 #!/usr/local/bin/python3 import json class Serializable(dict): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # hack to fix _json.so make_encoder serialize properly self.__setitem__('dummy', 1) def _myattrs(self): return [ (x, self._repr(getattr(self, x))) for x in self.__dir__() if x not in Serializable().__dir__() ] def _repr(self, value): if isinstance(value, (str, int, float, list, tuple, dict)): return value else: return repr(value) def __repr__(self): return '<%s.%s object at %s>' % ( self.__class__.__module__, self.__class__.__name__, hex(id(self)) ) def keys(self): return iter([x[0] for x in self._myattrs()]) def values(self): return iter([x[1] for x in self._myattrs()]) def items(self): return iter(self._myattrs()) 

现在让您的类与常规编码器一起序列化,扩展“Serializable”:

 class MySerializableClass(Serializable): attr_1 = 'first attribute' attr_2 = 23 def my_function(self): print('do something here') obj = MySerializableClass() 

print(obj)会打印如下内容:

 <__main__.MySerializableClass object at 0x1073525e8> 

print(json.dumps(obj, indent=4))将打印如下所示:

 { "attr_1": "first attribute", "attr_2": 23, "my_function": "<bound method MySerializableClass.my_function of <__main__.MySerializableClass object at 0x1073525e8>>" } 

我build议把这个入侵放到类定义中。 这样,一旦定义了类,它就支持JSON。 例:

 import json class MyClass( object ): def _jsonSupport( *args ): def default( self, xObject ): return { 'type': 'MyClass', 'name': xObject.name() } def objectHook( obj ): if 'type' not in obj: return obj if obj[ 'type' ] != 'MyClass': return obj return MyClass( obj[ 'name' ] ) json.JSONEncoder.default = default json._default_decoder = json.JSONDecoder( object_hook = objectHook ) _jsonSupport() def __init__( self, name ): self._name = name def name( self ): return self._name def __repr__( self ): return '<MyClass(name=%s)>' % self._name myObject = MyClass( 'Magneto' ) jsonString = json.dumps( [ myObject, 'some', { 'other': 'objects' } ] ) print "json representation:", jsonString decoded = json.loads( jsonString ) print "after decoding, our object is the first in the list", decoded[ 0 ] 

重写JSONEncoder().default是,你只能做一次。 如果你偶然发现一种不适用于这种模式的特殊数据types(就像你使用奇怪的编码)。 使用下面的模式,您可以始终使您的类JSON可序列化,只要您要序列化的类字段本身是可序列化的(并且可以添加到python列表,几乎没有任何东西)。 否则,你必须recursion地将相同的模式应用到你的json字段(或从中提取可序列化的数据):

 # base class that will make all derivatives JSON serializable: class JSONSerializable(list): # need to derive from a serializable class. def __init__(self, value = None): self = [ value ] def setJSONSerializableValue(self, value): self = [ value ] def getJSONSerializableValue(self): return self[1] if len(self) else None # derive your classes from JSONSerializable: class MyJSONSerializableObject(JSONSerializable): def __init__(self): # or any other function # .... # suppose your__json__field is the class member to be serialized. # it has to be serializable itself. # Every time you want to set it, call this function: self.setJSONSerializableValue(your__json__field) # ... # ... and when you need access to it, get this way: do_something_with_your__json__field(self.getJSONSerializableValue()) # now you have a JSON default-serializable class: a = MyJSONSerializableObject() print json.dumps(a) 

我不明白你为什么不能为你自己的类写一个serialize函数? 你在类中实现了自定义的编码器,并允许“人”调用序列化函数,基本上返回self.__dict__和去掉的函数。

编辑:

这个问题与我一致,最简单的方法是编写你自己的方法,并返回你想要的json序列化数据。 他们还build议尝试jsonpickle,但是现在,当内build正确的解决scheme时,您将为美添加额外的依赖关系。