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 isinstance
por 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.
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.
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()
yiteritems()
.
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()