¿La forma más sencilla de serializar un objeto de clase simple con simplejson?

Estoy intentando serializar una lista de objetos python con JSON (usando simplejson) y obtengo el error de que el objeto “no es serializable por JSON”.

La clase es una clase simple que tiene campos que solo son enteros, cadenas y flotantes, y hereda campos similares de una superclase principal, por ejemplo:

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) 

donde foo, bar son tipos simples como he mencionado anteriormente. Lo único difícil es que ChildClass a veces tiene un campo que se refiere a otro objeto (de un tipo que no es ParentClass o ChildClass).

¿Cuál es la forma más fácil de serializar esto como un objeto json con simplejson? ¿Es suficiente hacer que sea serializable como un diccionario? ¿Es la mejor manera de simplemente escribir un método dict para ChildClass? Finalmente, ¿tener el campo que se refiere a otro objeto complica significativamente las cosas? Si es así, puedo reescribir mi código para que solo tenga campos simples en las clases (como cadenas / flotadores, etc.)

gracias.

He usado esta estrategia en el pasado y estoy bastante contento con ella: codifique sus objetos personalizados como literales de objetos JSON (como los dict Python) con la siguiente estructura:

 { '__ClassName__': { ... } } 

Eso es esencialmente un dict un solo elemento cuya clave única es una cadena especial que especifica qué tipo de objeto se codifica, y cuyo valor es un dict de los atributos de la instancia. Si eso tiene sentido.

Una implementación muy simple de un codificador y un decodificador (simplificado a partir del código que realmente he usado) es así:

 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 

En esta implementación, se supone que los objetos que está codificando tendrán un método de clase from_dict() que sabe cómo recrear una instancia a partir de un dict decodificado de JSON.

Es fácil expandir el codificador y el decodificador para admitir tipos personalizados (por ejemplo, objetos de datetime ).

EDITAR , para responder a su edición: Lo bueno de una implementación como esta es que codificará y decodificará automáticamente las instancias de cualquier objeto encontrado en el mapeo de TYPES . Eso significa que manejará automáticamente un ChildClass así:

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

Eso debería resultar en JSON algo como lo siguiente:

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

Una instancia de una clase personalizada podría representarse como una cadena con formato JSON con la ayuda de la siguiente función:

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

Esta función producirá una cadena con formato JSON para

  • una instancia de una clase personalizada,

  • un diccionario que tiene instancias de clases personalizadas como hojas,

  • una lista de instancias de clases personalizadas

Si está utilizando Django, puede hacerlo fácilmente a través del módulo de serializadores de Django. Se puede encontrar más información aquí: https://docs.djangoproject.com/en/dev/topics/serialization/

Como se especifica en los documentos JSON de python // help(json.dumps) //>

Simplemente debe anular el método default() de JSONEncoder para proporcionar una conversión de tipos personalizada y pasarla como argumento cls .

Aquí hay uno que uso para cubrir los tipos de datos especiales de Mongo (datetime y 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) 

Llamándolo tan simple como

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

Tengo un problema similar pero la función json.dump no es llamada por mí. Por lo tanto, para hacer que MyClass JSON se pueda serializar sin darle un codificador personalizado a json.dump , tiene que Monkey parchear el codificador json.

Primero crea tu codificador en tu módulo my_module :

 import json class JSONEncoder(json.JSONEncoder): """To make MyClass JSON serializable you have to Monkey patch the json encoder with the following code: >>> import json >>> import my_module >>> json.JSONEncoder.default = my_module.JSONEncoder.default """ def default(self, o): """For JSON serialization.""" if isinstance(o, MyClass): return o.__repr__() else: return super(self,o) class MyClass: def __repr__(self): return "my class representation" 

Entonces, como se describe en el comentario, el parche mono del codificador json:

 import json import my_module json.JSONEncoder.default = my_module.JSONEncoder.default 

Ahora, incluso una llamada de json.dump en una biblioteca externa (donde no puede cambiar el parámetro cls ) funcionará para sus objetos my_module.MyClass .

Esto es una especie de piratería y estoy seguro de que hay muchas cosas que pueden estar equivocadas. Sin embargo, estaba produciendo un script simple y corrí el problema de que no quería crear una subclase de mi serializador json para serializar una lista de objetos modelo. Terminé usando la lista de comprensión

Deje: los activos = lista de objetos de modelo

Código:

 myJson = json.dumps([x.__dict__ for x in assets]) 

Hasta el momento parece haber funcionado con mucho encanto para mis necesidades.

Me siento un poco tonto con respecto a mis posibles 2 soluciones al releerlo ahora, por supuesto, cuando usa django-rest-framework, este marco tiene algunas funciones excelentes para este problema mencionado anteriormente.

Ver este ejemplo de vista modelo en su sitio web.

Si no está utilizando django-rest-framework, esto puede ayudar de todos modos:

Encontré 2 soluciones útiles para este problema en esta página: (¡Me gusta la segunda más!)

Posible solución 1 (o camino a seguir): David Chambers Design hizo una buena solución

Espero que a David no le importe que copie y pegue aquí su código de solución:

Defina un método de serialización en el modelo de la instancia:

 def toJSON(self): import simplejson return simplejson.dumps(dict([(attr, getattr(self, attr)) for attr in [f.name for f in self._meta.fields]])) 

e incluso extrajo el método anterior, así que es más legible:

 def toJSON(self): fields = [] for field in self._meta.fields: fields.append(field.name) d = {} for attr in fields: d[attr] = getattr(self, attr) import simplejson return simplejson.dumps(d) 

Por favor, tenga en cuenta que no es mi solución, todos los créditos van al enlace incluido. Sólo pensé que esto debería estar en la stack de desbordamiento.

Esto también podría implementarse en las respuestas anteriores.

Solución 2:

Mi solución preferible se encuentra en esta página:

http://www.traddicts.org/webdevelopment/flexible-and-simple-json-serialization-for-django/

Por cierto, vi al escritor de esta segunda y mejor solución: también está en stackoverflow:

Selaux

Espero que vea esto, ¿y podemos hablar de comenzar a implementar y mejorar su código en una solución abierta?