serializar objetos objeto guardar archivo python json

objetos - ¿Cómo cambiar el comportamiento de codificación json para el objeto python serializable?



modulo shelve python (13)

¿Podemos simplemente preprocesar el test_json , para que sea adecuado para su requerimiento? Es más fácil manipular un dict python que escribir un Encode inútil.

import datetime import json class mDict(dict): pass class mList(list): pass def prepare(obj): if isinstance(obj, datetime.datetime): return obj.isoformat() elif isinstance(obj, mDict): return {''orig'':obj , ''attrs'': vars(obj)} elif isinstance(obj, mList): return {''orig'':obj, ''attrs'': vars(obj)} else: return obj def preprocessor(toJson): ret ={} for key, value in toJson.items(): ret[key] = prepare(value) return ret if __name__ == ''__main__'': def test_debug_json(): games = mList([''mario'',''contra'',''tetris'']) games.src = ''console'' scores = mDict({''dp'':10,''pk'':45}) scores.processed = "unprocessed" test_json = { ''games'' : games, ''scores'' : scores , ''date'': datetime.datetime.now() } print(json.dumps(preprocessor(test_json))) test_debug_json()

Es fácil cambiar el formato de un objeto que no es serializable por JSON, por ejemplo, datetime.datetime.

Mi requisito, con fines de depuración, es alterar la forma en que algunos objetos personalizados se extendieron desde los básicos, como dict y list , se serializan en formato json. Código:

import datetime import json def json_debug_handler(obj): print("object received:") print type(obj) print("/n/n") if isinstance(obj, datetime.datetime): return obj.isoformat() elif isinstance(obj,mDict): return {''orig'':obj , ''attrs'': vars(obj)} elif isinstance(obj,mList): return {''orig'':obj, ''attrs'': vars(obj)} else: return None class mDict(dict): pass class mList(list): pass def test_debug_json(): games = mList([''mario'',''contra'',''tetris'']) games.src = ''console'' scores = mDict({''dp'':10,''pk'':45}) scores.processed = "unprocessed" test_json = { ''games'' : games , ''scores'' : scores , ''date'': datetime.datetime.now() } print(json.dumps(test_json,default=json_debug_handler)) if __name__ == ''__main__'': test_debug_json()

DEMO: http://ideone.com/hQJnLy

Salida:

{"date": "2013-05-07T01:03:13.098727", "games": ["mario", "contra", "tetris"], "scores": {"pk": 45, "dp": 10}}

Salida deseada:

{"date": "2013-05-07T01:03:13.098727", "games": { "orig": ["mario", "contra", "tetris"] ,"attrs" : { "src":"console"}} , "scores": { "orig": {"pk": 45, "dp": 10},"attrs": "processed":"unprocessed }}

¿El controlador default no funciona para objetos serializables? Si no, ¿cómo puedo anular esto, sin agregar métodos de JSON a las clases extendidas?

Además, existe esta versión del codificador JSON que no funciona:

class JsonDebugEncoder(json.JSONEncoder): def default(self,obj): if isinstance(obj, datetime.datetime): return obj.isoformat() elif isinstance(obj,mDict): return {''orig'':obj , ''attrs'': vars(obj)} elif isinstance(obj,mList): return {''orig'':obj, ''attrs'': vars(obj)} else: return json.JSONEncoder.default(self, obj)

Si hay un truco con pickle,__getstate__,__setstate__, y luego usando json.dumps sobre el objeto pickle.loads, estoy abierto a eso también, lo intenté, pero no funcionó.


¿Por qué no puedes simplemente crear un nuevo tipo de objeto para pasar al codificador? Tratar:

class MStuff(object): def __init__(self, content): self.content = content class mDict(MStuff): pass class mList(MStuff): pass def json_debug_handler(obj): print("object received:") print(type(obj)) print("/n/n") if isinstance(obj, datetime.datetime): return obj.isoformat() elif isinstance(obj,MStuff): attrs = {} for key in obj.__dict__: if not ( key.startswith("_") or key == "content"): attrs[key] = obj.__dict__[key] return {''orig'':obj.content , ''attrs'': attrs} else: return None

Puede agregar validación en mDict y mList si lo desea.


Como ya han señalado otros, al controlador predeterminado solo se le solicitan valores que no son uno de los tipos reconocidos. Mi solución sugerida para este problema es preprocesar el objeto que desea serializar, recurriendo a listas, tuplas y diccionarios, pero ajustando todos los demás valores en una clase personalizada.

Algo como esto:

def debug(obj): class Debug: def __init__(self,obj): self.originalObject = obj if obj.__class__ == list: return [debug(item) for item in obj] elif obj.__class__ == tuple: return (debug(item) for item in obj) elif obj.__class__ == dict: return dict((key,debug(obj[key])) for key in obj) else: return Debug(obj)

Llamarás a esta función antes de pasar tu objeto a json.dumps , así:

test_json = debug(test_json) print(json.dumps(test_json,default=json_debug_handler))

Tenga en cuenta que este código está buscando objetos cuya clase coincida exactamente con una lista, tupla o diccionario, por lo que los objetos personalizados que se extienden desde esos tipos se envolverán en lugar de analizarse. Como resultado, las listas, tuplas y diccionarios regulares se serializarán como de costumbre, pero todos los demás valores se transferirán al controlador predeterminado.

El resultado final de todo esto es que se garantiza que cada valor que alcanza el controlador predeterminado se envuelve en una de estas clases de depuración. Entonces, lo primero que querrás hacer es extraer el objeto original, así:

obj = obj.originalObject

Luego puede verificar el tipo de objeto original y manejar los tipos que necesiten procesamiento especial. Para todo lo demás, solo debe devolver el objeto original (por lo que la última devolución del controlador debe ser return obj no return None ).

def json_debug_handler(obj): obj = obj.originalObject # Add this line print("object received:") print type(obj) print("/n/n") if isinstance(obj, datetime.datetime): return obj.isoformat() elif isinstance(obj,mDict): return {''orig'':obj, ''attrs'': vars(obj)} elif isinstance(obj,mList): return {''orig'':obj, ''attrs'': vars(obj)} else: return obj # Change this line

Tenga en cuenta que este código no verifica los valores que no son serializables. Estos caerán a través del return obj final, luego serán rechazados por el serializador y volverán a pasar al manejador predeterminado nuevamente, solo que esta vez sin el envoltorio Debug.

Si necesita lidiar con ese escenario, puede agregar un cheque en la parte superior del controlador de esta manera:

if not hasattr(obj, ''originalObject''): return None

Demo de Ideone: http://ideone.com/tOloNq


Debería poder anular JSONEncoder.encode() :

class MyEncoder(JSONEncoder): def encode(self, o): if isinstance(o, dict): # directly call JSONEncoder rather than infinite-looping through self.encode() return JSONEncoder.encode(self, {''orig'': o, ''attrs'': vars(o)}) elif isinstance(o, list): return JSONEncoder.encode(self, {''orig'': o, ''attrs'': vars(o)}) else: return JSONEncoder.encode(self, o)

y luego si desea aplicar un parche en json.dumps se ve desde http://docs.buildbot.net/latest/reference/json-pysrc.html como si json._default_encoder reemplazar json._default_encoder con una instancia de MyEncoder .


En la línea de la sugerencia de FastTurtle, pero que requiere un código algo menor y una monkeying mucho más profunda, puede anular la isinstance sí mismo, a nivel mundial. Probablemente esta no es una buena idea, y puede romper algo. Pero funciona, ya que produce su salida requerida, y es bastante simple.

En primer lugar, antes de que json se importe en cualquier lugar , inserte un parche en el módulo incorporado para reemplazarlo isinstancepor uno que mienta, solo un poco, y solo en un contexto específico:

_original_isinstance = isinstance def _isinstance(obj, class_or_tuple): if ''_make_iterencode'' in globals(): if not _original_isinstance(class_or_tuple, tuple): class_or_tuple = (class_or_tuple,) for custom in mList, mDict: if _original_isinstance(obj, custom): return custom in class_or_tuple return _original_isinstance(obj, class_or_tuple) try: import builtins # Python 3 except ImportError: import __builtin__ as builtins # Python 2 builtins.isinstance = _isinstance

A continuación, cree su codificador personalizado, implementando su serialización personalizada y forzando el uso de _make_iterencode(ya que la versión c no se verá afectada por el monopatching):

class CustomEncoder(json.JSONEncoder): def iterencode(self, o, _one_shot = False): return super(CustomEncoder, self).iterencode(o, _one_shot=False) def default(self, obj): if isinstance(obj, datetime.datetime): return obj.isoformat() elif isinstance(obj,mDict): return {''orig'':dict(obj) , ''attrs'': vars(obj)} elif isinstance(obj,mList): return {''orig'':list(obj), ''attrs'': vars(obj)} else: return None

¡Y eso es todo lo que hay que hacer! Salida de Python 3 y Python 2 a continuación.

Python 3.6.3 (default, Oct 10 2017, 21:06:48) ... >>> from test import test_debug_json >>> test_debug_json() {"games": {"orig": ["mario", "contra", "tetris"], "attrs": {"src": "console"}}, "scores": {"orig": {"dp": 10, "pk": 45}, "attrs": {"processed": "unprocessed"}}, "date": "2018-01-27T13:56:15.666655"} Python 2.7.13 (default, May 9 2017, 12:06:13) ... >>> from test import test_debug_json >>> test_debug_json() {"date": "2018-01-27T13:57:04.681664", "games": {"attrs": {"src": "console"}, "orig": ["mario", "contra", "tetris"]}, "scores": {"attrs": {"processed": "unprocessed"}, "orig": {"pk": 45, "dp": 10}}}


La función predeterminada solo se invoca cuando el nodo que se va a descargar no se puede serializar de forma nativa, y las clases de mDict se serializan tal como están. Aquí hay una pequeña demostración que muestra cuándo se llama por defecto y cuándo no:

import json def serializer(obj): print ''serializer called'' return str(obj) class mDict(dict): pass class mSet(set): pass d = mDict(dict(a=1)) print json.dumps(d, default=serializer) s = mSet({1, 2, 3,}) print json.dumps(s, default=serializer)

Y el resultado:

{"a": 1} serializer called "mSet([1, 2, 3])"

Tenga en cuenta que los conjuntos no son serializables de forma nativa, pero sí lo son los dictados.

Como tus clases m___ son serializables, nunca se llama a tu controlador.

Actualización n. ° 1 -----

Podrías cambiar el código del codificador JSON. Los detalles de cómo hacer esto dependen de la implementación JSON que esté utilizando. Por ejemplo, en simplejson, el código relevante es este, en encode.py:

def _iterencode(o, _current_indent_level): ... for_json = _for_json and getattr(o, ''for_json'', None) if for_json and callable(for_json): ... elif isinstance(o, list): ... else: _asdict = _namedtuple_as_object and getattr(o, ''_asdict'', None) if _asdict and callable(_asdict): for chunk in _iterencode_dict(_asdict(), _current_indent_level): yield chunk elif (_tuple_as_array and isinstance(o, tuple)): ... elif isinstance(o, dict): ... elif _use_decimal and isinstance(o, Decimal): ... else: ... o = _default(o) for chunk in _iterencode(o, _current_indent_level): yield chunk ...

En otras palabras, hay un comportamiento cableado que llama a los valores predeterminados solo cuando el nodo codificado no es uno de los tipos base reconocidos. Puede anular esto de una de varias maneras:

1 - subclase JSONEncoder como lo hizo anteriormente, pero agregue un parámetro a su inicializador que especifique la función que se usará en lugar del estándar _make_iterencode, en el que agrega una prueba que llamaría por defecto para las clases que cumplan con sus criterios. Este es un enfoque limpio ya que no está cambiando el módulo JSON, pero estaría reiterando una gran cantidad de código del _make_iterencode original. (Otras variaciones en este enfoque incluyen monkeypatching _make_iterencode o su subfunción _iterencode_dict).

2 - modifique el origen del módulo JSON y use la constante __debug__ para cambiar el comportamiento:

def _iterencode(o, _current_indent_level): ... for_json = _for_json and getattr(o, ''for_json'', None) if for_json and callable(for_json): ... elif isinstance(o, list): ... ## added code below elif __debug__: o = _default(o) for chunk in _iterencode(o, _current_indent_level): yield chunk ## added code above else: ...

Idealmente, la clase JSONEncoder proporcionaría un parámetro para especificar "usar valores predeterminados para todos los tipos", pero no es así. Lo anterior es un simple cambio de una sola vez que hace lo que estás buscando.


Parece que para lograr el comportamiento que deseas, con las restricciones dadas, tendrás que profundizar un poco en la clase JSONEncoder . A continuación he escrito un JSONEncoder personalizado JSONEncoder que anula el método iterencode para pasar un método personalizado de isinstance a _make_iterencode . No es la cosa más limpia del mundo, pero parece ser la mejor dadas las opciones y mantiene la personalización al mínimo.

# customencoder.py from json.encoder import (_make_iterencode, JSONEncoder, encode_basestring_ascii, FLOAT_REPR, INFINITY, c_make_encoder, encode_basestring) class CustomObjectEncoder(JSONEncoder): def iterencode(self, o, _one_shot=False): """ Most of the original method has been left untouched. _one_shot is forced to False to prevent c_make_encoder from being used. c_make_encoder is a funcion defined in C, so it''s easier to avoid using it than overriding/redefining it. The keyword argument isinstance for _make_iterencode has been set to self.isinstance. This allows for a custom isinstance function to be defined, which can be used to defer the serialization of custom objects to the default method. """ # Force the use of _make_iterencode instead of c_make_encoder _one_shot = False if self.check_circular: markers = {} else: markers = None if self.ensure_ascii: _encoder = encode_basestring_ascii else: _encoder = encode_basestring if self.encoding != ''utf-8'': def _encoder(o, _orig_encoder=_encoder, _encoding=self.encoding): if isinstance(o, str): o = o.decode(_encoding) return _orig_encoder(o) def floatstr(o, allow_nan=self.allow_nan, _repr=FLOAT_REPR, _inf=INFINITY, _neginf=-INFINITY): if o != o: text = ''NaN'' elif o == _inf: text = ''Infinity'' elif o == _neginf: text = ''-Infinity'' else: return _repr(o) if not allow_nan: raise ValueError( "Out of range float values are not JSON compliant: " + repr(o)) return text # Instead of forcing _one_shot to False, you can also just # remove the first part of this conditional statement and only # call _make_iterencode if (_one_shot and c_make_encoder is not None and self.indent is None and not self.sort_keys): _iterencode = c_make_encoder( markers, self.default, _encoder, self.indent, self.key_separator, self.item_separator, self.sort_keys, self.skipkeys, self.allow_nan) else: _iterencode = _make_iterencode( markers, self.default, _encoder, self.indent, floatstr, self.key_separator, self.item_separator, self.sort_keys, self.skipkeys, _one_shot, isinstance=self.isinstance) return _iterencode(o, 0)

Ahora puede subclasificar CustomObjectEncoder para serializar correctamente sus objetos personalizados. CustomObjectEncoder también puede hacer cosas interesantes como manejar objetos anidados.

# test.py import json import datetime from customencoder import CustomObjectEncoder class MyEncoder(CustomObjectEncoder): def isinstance(self, obj, cls): if isinstance(obj, (mList, mDict)): return False return isinstance(obj, cls) def default(self, obj): """ Defines custom serialization. To avoid circular references, any object that will always fail self.isinstance must be converted to something that is deserializable here. """ if isinstance(obj, datetime.datetime): return obj.isoformat() elif isinstance(obj, mDict): return {"orig": dict(obj), "attrs": vars(obj)} elif isinstance(obj, mList): return {"orig": list(obj), "attrs": vars(obj)} else: return None class mList(list): pass class mDict(dict): pass def main(): zelda = mList([''zelda'']) zelda.src = "oldschool" games = mList([''mario'', ''contra'', ''tetris'', zelda]) games.src = ''console'' scores = mDict({''dp'': 10, ''pk'': 45}) scores.processed = "unprocessed" test_json = {''games'': games, ''scores'': scores, ''date'': datetime.datetime.now()} print(json.dumps(test_json, cls=MyEncoder)) if __name__ == ''__main__'': main()


Pruebe lo siguiente. Produce el resultado que desea y se ve relativamente simple. La única diferencia real de su clase de codificador es que debemos anular tanto los métodos de decodificación como de codificación (ya que este último todavía se llama para los tipos que el codificador sabe cómo manejar).

import json import datetime class JSONDebugEncoder(json.JSONEncoder): # transform objects known to JSONEncoder here def encode(self, o, *args, **kw): for_json = o if isinstance(o, mDict): for_json = { ''orig'' : o, ''attrs'' : vars(o) } elif isinstance(o, mList): for_json = { ''orig'' : o, ''attrs'' : vars(o) } return super(JSONDebugEncoder, self).encode(for_json, *args, **kw) # handle objects not known to JSONEncoder here def default(self, o, *args, **kw): if isinstance(o, datetime.datetime): return o.isoformat() else: return super(JSONDebugEncoder, self).default(o, *args, **kw) class mDict(dict): pass class mList(list): pass def test_debug_json(): games = mList([''mario'',''contra'',''tetris'']) games.src = ''console'' scores = mDict({''dp'':10,''pk'':45}) scores.processed = "unprocessed" test_json = { ''games'' : games , ''scores'' : scores , ''date'': datetime.datetime.now() } print(json.dumps(test_json,cls=JSONDebugEncoder)) if __name__ == ''__main__'': test_debug_json()


Si define esto para anular __instancecheck__ :

def strict_check(builtin): ''''''creates a new class from the builtin whose instance check method can be overridden to renounce particular types'''''' class BuiltIn(type): def __instancecheck__(self, other): print ''instance'', self, type(other), other if type(other) in strict_check.blacklist: return False return builtin.__instancecheck__(other) # construct a class, whose instance check method is known. return BuiltIn(''strict_%s'' % builtin.__name__, (builtin,), dict()) # for safety, define it here. strict_check.blacklist = ()

luego parche json.encoder esta manera para anular _make_iterencode.func_defaults :

# modify json encoder to use some new list/dict attr. import json.encoder # save old stuff, never know when you need it. old_defaults = json.encoder._make_iterencode.func_defaults old_encoder = json.encoder.c_make_encoder encoder_defaults = list(json.encoder._make_iterencode.func_defaults) for index, default in enumerate(encoder_defaults): if default in (list, dict): encoder_defaults[index] = strict_check(default) # change the defaults for _make_iterencode. json.encoder._make_iterencode.func_defaults = tuple(encoder_defaults) # disable C extension. json.encoder.c_make_encoder = None

... tu ejemplo casi funcionaría textualmente:

import datetime import json def json_debug_handler(obj): print("object received:") print type(obj) print("/n/n") if isinstance(obj, datetime.datetime): return obj.isoformat() elif isinstance(obj,mDict): # degrade obj to more primitive dict() # to avoid cycles in the encoding. return {''orig'': dict(obj) , ''attrs'': vars(obj)} elif isinstance(obj,mList): # degrade obj to more primitive list() # to avoid cycles in the encoding. return {''orig'': list(obj), ''attrs'': vars(obj)} else: return None class mDict(dict): pass class mList(list): pass # set the stuff we want to process differently. strict_check.blacklist = (mDict, mList) def test_debug_json(): global test_json games = mList([''mario'',''contra'',''tetris'']) games.src = ''console'' scores = mDict({''dp'':10,''pk'':45}) scores.processed = "unprocessed" test_json = { ''games'' : games , ''scores'' : scores , ''date'': datetime.datetime.now() } print(json.dumps(test_json,default=json_debug_handler)) if __name__ == ''__main__'': test_debug_json()

Las cosas que necesitaba cambiar eran asegurarme de que no hubiera ciclos:

elif isinstance(obj,mDict): # degrade obj to more primitive dict() # to avoid cycles in the encoding. return {''orig'': dict(obj) , ''attrs'': vars(obj)} elif isinstance(obj,mList): # degrade obj to more primitive list() # to avoid cycles in the encoding. return {''orig'': list(obj), ''attrs'': vars(obj)}

y agregue esto en algún lugar antes de test_debug_json :

# set the stuff we want to process differently. strict_check.blacklist = (mDict, mList)

aquí está mi salida de consola:

>>> test_debug_json() instance <class ''__main__.strict_list''> <type ''dict''> {''date'': datetime.datetime(2013, 7, 17, 12, 4, 40, 950637), ''games'': [''mario'', ''contra'', ''tetris''], ''scores'': {''pk'': 45, ''dp'': 10}} instance <class ''__main__.strict_dict''> <type ''dict''> {''date'': datetime.datetime(2013, 7, 17, 12, 4, 40, 950637), ''games'': [''mario'', ''contra'', ''tetris''], ''scores'': {''pk'': 45, ''dp'': 10}} instance <class ''__main__.strict_list''> <type ''datetime.datetime''> 2013-07-17 12:04:40.950637 instance <class ''__main__.strict_dict''> <type ''datetime.datetime''> 2013-07-17 12:04:40.950637 instance <class ''__main__.strict_list''> <type ''datetime.datetime''> 2013-07-17 12:04:40.950637 instance <class ''__main__.strict_dict''> <type ''datetime.datetime''> 2013-07-17 12:04:40.950637 object received: <type ''datetime.datetime''> instance <class ''__main__.strict_list''> <class ''__main__.mList''> [''mario'', ''contra'', ''tetris''] instance <class ''__main__.strict_dict''> <class ''__main__.mList''> [''mario'', ''contra'', ''tetris''] instance <class ''__main__.strict_list''> <class ''__main__.mList''> [''mario'', ''contra'', ''tetris''] instance <class ''__main__.strict_dict''> <class ''__main__.mList''> [''mario'', ''contra'', ''tetris''] object received: <class ''__main__.mList''> instance <class ''__main__.strict_list''> <type ''dict''> {''attrs'': {''src'': ''console''}, ''orig'': [''mario'', ''contra'', ''tetris'']} instance <class ''__main__.strict_dict''> <type ''dict''> {''attrs'': {''src'': ''console''}, ''orig'': [''mario'', ''contra'', ''tetris'']} instance <class ''__main__.strict_list''> <type ''dict''> {''src'': ''console''} instance <class ''__main__.strict_dict''> <type ''dict''> {''src'': ''console''} instance <class ''__main__.strict_list''> <type ''list''> [''mario'', ''contra'', ''tetris''] instance <class ''__main__.strict_list''> <class ''__main__.mDict''> {''pk'': 45, ''dp'': 10} instance <class ''__main__.strict_dict''> <class ''__main__.mDict''> {''pk'': 45, ''dp'': 10} instance <class ''__main__.strict_list''> <class ''__main__.mDict''> {''pk'': 45, ''dp'': 10} instance <class ''__main__.strict_dict''> <class ''__main__.mDict''> {''pk'': 45, ''dp'': 10} object received: <class ''__main__.mDict''> instance <class ''__main__.strict_list''> <type ''dict''> {''attrs'': {''processed'': ''unprocessed''}, ''orig'': {''pk'': 45, ''dp'': 10}} instance <class ''__main__.strict_dict''> <type ''dict''> {''attrs'': {''processed'': ''unprocessed''}, ''orig'': {''pk'': 45, ''dp'': 10}} instance <class ''__main__.strict_list''> <type ''dict''> {''processed'': ''unprocessed''} instance <class ''__main__.strict_dict''> <type ''dict''> {''processed'': ''unprocessed''} instance <class ''__main__.strict_list''> <type ''dict''> {''pk'': 45, ''dp'': 10} instance <class ''__main__.strict_dict''> <type ''dict''> {''pk'': 45, ''dp'': 10} {"date": "2013-07-17T12:04:40.950637", "games": {"attrs": {"src": "console"}, "orig": ["mario", "contra", "tetris"]}, "scores": {"attrs": {"processed": "unprocessed"}, "orig": {"pk": 45, "dp": 10}}}


Si puede cambiar la forma en que se llama json.dumps . Puede hacer todo el procesamiento requerido antes de que el codificador JSON lo tenga en sus manos. Esta versión no utiliza ningún tipo de copia y editará las estructuras en el lugar. Puede agregar copy() si es necesario.

import datetime import json import collections def json_debug_handler(obj): print("object received:") print type(obj) print("/n/n") if isinstance(obj, collections.Mapping): for key, value in obj.iteritems(): if isinstance(value, (collections.Mapping, collections.MutableSequence)): value = json_debug_handler(value) obj[key] = convert(value) elif isinstance(obj, collections.MutableSequence): for index, value in enumerate(obj): if isinstance(value, (collections.Mapping, collections.MutableSequence)): value = json_debug_handler(value) obj[index] = convert(value) return obj def convert(obj): if isinstance(obj, datetime.datetime): return obj.isoformat() elif isinstance(obj,mDict): return {''orig'':obj , ''attrs'': vars(obj)} elif isinstance(obj,mList): return {''orig'':obj, ''attrs'': vars(obj)} else: return obj class mDict(dict): pass class mList(list): pass def test_debug_json(): games = mList([''mario'',''contra'',''tetris'']) games.src = ''console'' scores = mDict({''dp'':10,''pk'':45}) scores.processed = "qunprocessed" test_json = { ''games'' : games , ''scores'' : scores , ''date'': datetime.datetime.now() } print(json.dumps(json_debug_handler(test_json))) if __name__ == ''__main__'': test_debug_json()

Llama a json_debug_handler en el objeto que está serializando antes de pasarlo a json.dumps . Con este patrón también puede invertir fácilmente los cambios y / o agregar reglas de conversión adicionales.

editar:

Si no puede cambiar cómo se llama a json.dumps , siempre puede hacer un parche en monopatín para hacer lo que desee. Como hacer esto:

json.dumps = lambda obj, *args, **kwargs: json.dumps(json_debug_handler(obj), *args, **kwargs)


Si solo está buscando la serialización y no la deserialización, entonces puede procesar el objeto antes de enviarlo a json.dumps . Ver ejemplo abajo

import datetime import json def is_inherited_from(obj, objtype): return isinstance(obj, objtype) and not type(obj).__mro__[0] == objtype def process_object(data): if isinstance(data, list): if is_inherited_from(data, list): return process_object({"orig": list(data), "attrs": vars(data)}) new_data = [] for d in data: new_data.append(process_object(d)) elif isinstance(data, tuple): if is_inherited_from(data, tuple): return process_object({"orig": tuple(data), "attrs": vars(data)}) new_data = [] for d in data: new_data.append(process_object(d)) return tuple(new_data) elif isinstance(data, dict): if is_inherited_from(data, dict): return process_object({"orig": list(data), "attrs": vars(data)}) new_data = {} for k, v in data.items(): new_data[k] = process_object(v) else: return data return new_data def json_debug_handler(obj): print("object received:") print("/n/n") if isinstance(obj, datetime.datetime): return obj.isoformat() class mDict(dict): pass class mList(list): pass def test_debug_json(): games = mList([''mario'', ''contra'', ''tetris'']) games.src = ''console'' scores = mDict({''dp'': 10, ''pk'': 45}) scores.processed = "unprocessed" test_json = {''games'': games, ''scores'': scores, ''date'': datetime.datetime.now()} new_object = process_object(test_json) print(json.dumps(new_object, default=json_debug_handler)) if __name__ == ''__main__'': test_debug_json()

El resultado del mismo es

{"juegos": {"orig": ["mario", "contra", "tetris"], "attrs": {"src": "console"}}, "scores": {"orig": [" dp "," pk "]," attrs ": {" procesado ":" sin procesar "}}," fecha ":" 2018-01-24T12: 59: 36.581689 "}

También es posible anular JSONEncoder, pero debido a que utiliza métodos anidados, sería complejo y requeriría técnicas discutidas a continuación.

¿Puede parchear * solo * una función anidada con cierre, o debe repetirse toda la función externa?

Como quieres mantener las cosas simples, yo no recomendaría ir por esa ruta


La respuesta de FastTurtle podría ser una solución mucho más limpia.

Aquí hay algo cerca de lo que quiere basado en la técnica explicada en mi pregunta / respuesta: anulación de la codificación JSON anidada de objetos admitidos por defecto heredados como dict, list

import json import datetime class mDict(dict): pass class mList(list): pass class JsonDebugEncoder(json.JSONEncoder): def _iterencode(self, o, markers=None): if isinstance(o, mDict): yield ''{"__mDict__": '' # Encode dictionary yield ''{"orig": '' for chunk in super(JsonDebugEncoder, self)._iterencode(o, markers): yield chunk yield '', '' # / End of Encode dictionary # Encode attributes yield ''"attr": '' for key, value in o.__dict__.iteritems(): yield ''{"'' + key + ''": '' for chunk in super(JsonDebugEncoder, self)._iterencode(value, markers): yield chunk yield ''}'' yield ''}'' # / End of Encode attributes yield ''}'' elif isinstance(o, mList): yield ''{"__mList__": '' # Encode list yield ''{"orig": '' for chunk in super(JsonDebugEncoder, self)._iterencode(o, markers): yield chunk yield '', '' # / End of Encode list # Encode attributes yield ''"attr": '' for key, value in o.__dict__.iteritems(): yield ''{"'' + key + ''": '' for chunk in super(JsonDebugEncoder, self)._iterencode(value, markers): yield chunk yield ''}'' yield ''}'' # / End of Encode attributes yield ''}'' else: for chunk in super(JsonDebugEncoder, self)._iterencode(o, markers=markers): yield chunk def default(self, obj): if isinstance(obj, datetime.datetime): return obj.isoformat() class JsonDebugDecoder(json.JSONDecoder): def decode(self, s): obj = super(JsonDebugDecoder, self).decode(s) obj = self.recursiveObjectDecode(obj) return obj def recursiveObjectDecode(self, obj): if isinstance(obj, dict): decoders = [("__mList__", self.mListDecode), ("__mDict__", self.mDictDecode)] for placeholder, decoder in decoders: if placeholder in obj: # We assume it''s supposed to be converted return decoder(obj[placeholder]) else: for k in obj: obj[k] = self.recursiveObjectDecode(obj[k]) elif isinstance(obj, list): for x in range(len(obj)): obj[x] = self.recursiveObjectDecode(obj[x]) return obj def mDictDecode(self, o): res = mDict() for key, value in o[''orig''].iteritems(): res[key] = self.recursiveObjectDecode(value) for key, value in o[''attr''].iteritems(): res.__dict__[key] = self.recursiveObjectDecode(value) return res def mListDecode(self, o): res = mList() for value in o[''orig'']: res.append(self.recursiveObjectDecode(value)) for key, value in o[''attr''].iteritems(): res.__dict__[key] = self.recursiveObjectDecode(value) return res def test_debug_json(): games = mList([''mario'',''contra'',''tetris'']) games.src = ''console'' scores = mDict({''dp'':10,''pk'':45}) scores.processed = "unprocessed" test_json = { ''games'' : games, ''scores'' : scores ,''date'': datetime.datetime.now() } jsonDump = json.dumps(test_json, cls=JsonDebugEncoder) print jsonDump test_pyObject = json.loads(jsonDump, cls=JsonDebugDecoder) print test_pyObject if __name__ == ''__main__'': test_debug_json()

Esto resulta en:

{"date": "2013-05-06T22:28:08.967000", "games": {"__mList__": {"orig": ["mario", "contra", "tetris"], "attr": {"src": "console"}}}, "scores": {"__mDict__": {"orig": {"pk": 45, "dp": 10}, "attr": {"processed": "unprocessed"}}}}

De esta forma, puede codificarlo y decodificarlo de nuevo al objeto Python del que proviene.

EDITAR:

Aquí hay una versión que realmente lo codifica para el resultado que quería y también puede decodificarlo. Siempre que un diccionario contenga ''orig'' y ''attr'', comprobará si ''orig'' contiene un diccionario o una lista, y si lo hace convertirá el objeto de nuevo a mDict o mList.

import json import datetime class mDict(dict): pass class mList(list): pass class JsonDebugEncoder(json.JSONEncoder): def _iterencode(self, o, markers=None): if isinstance(o, mDict): # Encode mDict yield ''{"orig": '' for chunk in super(JsonDebugEncoder, self)._iterencode(o, markers): yield chunk yield '', '' yield ''"attr": '' for key, value in o.__dict__.iteritems(): yield ''{"'' + key + ''": '' for chunk in super(JsonDebugEncoder, self)._iterencode(value, markers): yield chunk yield ''}'' yield ''}'' # / End of Encode attributes elif isinstance(o, mList): # Encode mList yield ''{"orig": '' for chunk in super(JsonDebugEncoder, self)._iterencode(o, markers): yield chunk yield '', '' yield ''"attr": '' for key, value in o.__dict__.iteritems(): yield ''{"'' + key + ''": '' for chunk in super(JsonDebugEncoder, self)._iterencode(value, markers): yield chunk yield ''}'' yield ''}'' else: for chunk in super(JsonDebugEncoder, self)._iterencode(o, markers=markers): yield chunk def default(self, obj): if isinstance(obj, datetime.datetime): # Encode datetime return obj.isoformat() class JsonDebugDecoder(json.JSONDecoder): def decode(self, s): obj = super(JsonDebugDecoder, self).decode(s) obj = self.recursiveObjectDecode(obj) return obj def recursiveObjectDecode(self, obj): if isinstance(obj, dict): if "orig" in obj and "attr" in obj and isinstance(obj["orig"], list): return self.mListDecode(obj) elif "orig" in obj and "attr" in obj and isinstance(obj[''orig''], dict): return self.mDictDecode(obj) else: for k in obj: obj[k] = self.recursiveObjectDecode(obj[k]) elif isinstance(obj, list): for x in range(len(obj)): obj[x] = self.recursiveObjectDecode(obj[x]) return obj def mDictDecode(self, o): res = mDict() for key, value in o[''orig''].iteritems(): res[key] = self.recursiveObjectDecode(value) for key, value in o[''attr''].iteritems(): res.__dict__[key] = self.recursiveObjectDecode(value) return res def mListDecode(self, o): res = mList() for value in o[''orig'']: res.append(self.recursiveObjectDecode(value)) for key, value in o[''attr''].iteritems(): res.__dict__[key] = self.recursiveObjectDecode(value) return res def test_debug_json(): games = mList([''mario'',''contra'',''tetris'']) games.src = ''console'' scores = mDict({''dp'':10,''pk'':45}) scores.processed = "unprocessed" test_json = { ''games'' : games, ''scores'' : scores ,''date'': datetime.datetime.now() } jsonDump = json.dumps(test_json, cls=JsonDebugEncoder) print jsonDump test_pyObject = json.loads(jsonDump, cls=JsonDebugDecoder) print test_pyObject print test_pyObject[''games''].src if __name__ == ''__main__'': test_debug_json()

Aquí hay más información sobre el resultado:

# Encoded {"date": "2013-05-06T22:41:35.498000", "games": {"orig": ["mario", "contra", "tetris"], "attr": {"src": "console"}}, "scores": {"orig": {"pk": 45, "dp": 10}, "attr": {"processed": "unprocessed"}}} # Decoded (''games'' contains the mList with the src attribute and ''scores'' contains the mDict processed attribute) # Note that printing the python objects doesn''t directly show the processed and src attributes, as seen below. {u''date'': u''2013-05-06T22:41:35.498000'', u''games'': [u''mario'', u''contra'', u''tetris''], u''scores'': {u''pk'': 45, u''dp'': 10}}

Lo siento por cualquier mala convención de nombres, es una configuración rápida. ;)

Nota: La fecha y hora no se decodifica de nuevo a la representación de Python. Implementando eso se podría hacer buscando cualquier clave dict que se llame ''fecha'' y que contenga una representación de cadena válida de una fecha y hora.


Intento cambiar la prioridad de resolución predeterminada y cambiar los resultados predeterminados del iterador para lograr sus propósitos.

  1. cambie la prioridad de resolución predeterminada, ejecutada antes de verificar todo tipo estándar:

    Hereda el json.JSONEncoder y anula el iterencode()método.

    Todos los valores deben estar envueltos por el tipo ValueWrapper , evite que los valores se resuelvan por resolución estándar por defecto.

  2. cambiar la salida predeterminada del iterador;

    Implementar tres clases de contenedor personalizado ValueWrapper , ListWrapper y DictWrapper . El implementador de ListWrapper __iter__()y el implemento DictWrapper __iter__(), items()y iteritems().

import datetime import json class DebugJsonEncoder(json.JSONEncoder): def iterencode(self, o, _one_shot=False): default_resolver = self.default # Rewrites the default resolve, self.default(), with the custom resolver. # It will process the Wrapper classes def _resolve(o): if isinstance(o, ValueWrapper): # Calls custom resolver precede others. Due to the _make_iterencode() # call the custom resolver following by all standard type verifying # failed. But we want custom resolver can be executed by all standard # verifying. # see https://github.com/python/cpython/blob/2.7/Lib/json/encoder.py#L442 result = default_resolver(o.data) if (o.data is not None) and (result is not None): return result elif isinstance(o.data, (list, tuple)): return ListWrapper(o.data) elif isinstance(o.data, dict): return DictWrapper(o.data) else: return o.data else: return default_resolver(o) # re-assign the default resolver self.default with custom resolver. # see https://github.com/python/cpython/blob/2.7/Lib/json/encoder.py#L161 self.default = _resolve # The input value must be wrapped by ValueWrapper, avoid the values are # resolved by the standard resolvers. # The last one arguemnt _one_shot must be False, we want to encode with # _make_iterencode(). # see https://github.com/python/cpython/blob/2.7/Lib/json/encoder.py#L259 return json.JSONEncoder.iterencode(self, _resolve(ValueWrapper(o)), False) class ValueWrapper(): """ a wrapper wrapped the given object """ def __init__(self, o): self.data = o class ListWrapper(ValueWrapper, list): """ a wrapper wrapped the given list """ def __init__(self, o): ValueWrapper.__init__(self, o) # see https://github.com/python/cpython/blob/2.7/Lib/json/encoder.py#L307 def __iter__(self): for chunk in self.data: yield ValueWrapper(chunk) class DictWrapper(ValueWrapper, dict): """ a wrapper wrapped the given dict """ def __init__(self, d): dict.__init__(self, d) def __iter__(self): for key, value in dict.items(self): yield key, ValueWrapper(value) # see https://github.com/python/cpython/blob/2.7/Lib/json/encoder.py#L361 def items(self): for key, value in dict.items(self): yield key, ValueWrapper(value) # see https://github.com/python/cpython/blob/2.7/Lib/json/encoder.py#L363 def iteritems(self): for key, value in dict.iteritems(self): yield key, ValueWrapper(value) def json_debug_handler(obj): print("object received:") print type(obj) print("/n/n") if isinstance(obj, datetime.datetime): return obj.isoformat() elif isinstance(obj,mDict): return {''orig'':obj , ''attrs'': vars(obj)} elif isinstance(obj,mList): return {''orig'':obj, ''attrs'': vars(obj)} else: return None class mDict(dict): pass class mList(list): pass def test_debug_json(): games = mList([''mario'',''contra'',''tetris'']) games.src = ''console'' scores = mDict({''dp'':10,''pk'':45}) scores.processed = "unprocessed" test_json = { ''games'' : games , ''scores'' : scores , ''date'': datetime.datetime.now(), ''default'': None} print(json.dumps(test_json,cls=DebugJsonEncoder,default=json_debug_handler)) if __name__ == ''__main__'': test_debug_json()