python - query - Como hacer una clase JSON serializable.
python return json (22)
¿Cómo hacer una clase Python serializable?
Una clase simple:
class FileItem:
def __init__(self, fname):
self.fname = fname
¿Qué debo hacer para poder obtener una salida de:
json.dumps()
Sin un error (la FileItem instance at ... is not JSON serializable
)
¿Tienes una idea sobre el resultado esperado? Por ejemplo, ¿hará esto?
>>> f = FileItem("/foo/bar")
>>> magic(f)
''{"fname": "/foo/bar"}''
En ese caso, simplemente puede llamar a json.dumps(f.__dict__)
.
Si desea una salida más personalizada, tendrá que subclasificar JSONEncoder
e implementar su propia serialización personalizada.
Para un ejemplo trivial, ver más abajo.
>>> from json import JSONEncoder
>>> class MyEncoder(JSONEncoder):
def default(self, o):
return o.__dict__
>>> MyEncoder().encode(f)
''{"fname": "/foo/bar"}''
Luego pasas esta clase al método json.dumps()
como cls
kwarg:
json.dumps(cls=MyEncoder)
Si también desea descodificar, deberá proporcionar un object_hook
personalizado a la clase json.dumps() . Por ejemplo
>>> def from_json(json_object):
if ''fname'' in json_object:
return FileItem(json_object[''fname''])
>>> f = JSONDecoder(object_hook = from_json).decode(''{"fname": "/foo/bar"}'')
>>> f
<__main__.FileItem object at 0x9337fac>
>>>
Aquí están mis 3 centavos ...
Esto demuestra la serialización json explícita para un objeto python parecido a un árbol.
Nota: Si realmente quisiera un código como este, podría usar la clase FilePath retorcida .
import json, sys, os
class File:
def __init__(self, path):
self.path = path
def isdir(self):
return os.path.isdir(self.path)
def isfile(self):
return os.path.isfile(self.path)
def children(self):
return [File(os.path.join(self.path, f))
for f in os.listdir(self.path)]
def getsize(self):
return os.path.getsize(self.path)
def getModificationTime(self):
return os.path.getmtime(self.path)
def _default(o):
d = {}
d[''path''] = o.path
d[''isFile''] = o.isfile()
d[''isDir''] = o.isdir()
d[''mtime''] = int(o.getModificationTime())
d[''size''] = o.getsize() if o.isfile() else 0
if o.isdir(): d[''children''] = o.children()
return d
folder = os.path.abspath(''.'')
json.dump(File(folder), sys.stdout, default=_default)
Aquí hay una solución simple para una característica simple:
.toJSON()
Método
En lugar de una clase serializable JSON, implemente un método de serializador:
import json
class Object:
def toJSON(self):
return json.dumps(self, default=lambda o: o.__dict__,
sort_keys=True, indent=4)
Así que solo lo llamas para serializar:
me = Object()
me.name = "Onur"
me.age = 35
me.dog = Object()
me.dog.name = "Apollo"
print(me.toJSON())
saldrá:
{
"age": 35,
"dog": {
"name": "Apollo"
},
"name": "Onur"
}
Elegí usar decoradores para resolver el problema de serialización de objetos de fecha y hora. Aquí está mi código:
#myjson.py
#Author: jmooremcc 7/16/2017
import json
from datetime import datetime, date, time, timedelta
"""
This module uses decorators to serialize date objects using json
The filename is myjson.py
In another module you simply add the following import statement:
from myjson import json
json.dumps and json.dump will then correctly serialize datetime and date
objects
"""
def json_serial(obj):
"""JSON serializer for objects not serializable by default json code"""
if isinstance(obj, (datetime, date)):
serial = str(obj)
return serial
raise TypeError ("Type %s not serializable" % type(obj))
def FixDumps(fn):
def hook(obj):
return fn(obj, default=json_serial)
return hook
def FixDump(fn):
def hook(obj, fp):
return fn(obj,fp, default=json_serial)
return hook
json.dumps=FixDumps(json.dumps)
json.dump=FixDump(json.dump)
if __name__=="__main__":
today=datetime.now()
data={''atime'':today, ''greet'':''Hello''}
str=json.dumps(data)
print str
Al importar el módulo anterior, mis otros módulos usan json de manera normal (sin especificar la palabra clave predeterminada) para serializar los datos que contienen objetos de fecha y hora. El código del serializador de fecha y hora se llama automáticamente para json.dumps y json.dump.
Encontré este problema el otro día e implementé una versión más general de un codificador para objetos de Python que puede manejar objetos anidados y campos heredados :
import json
import inspect
class ObjectEncoder(json.JSONEncoder):
def default(self, obj):
if hasattr(obj, "to_json"):
return self.default(obj.to_json())
elif hasattr(obj, "__dict__"):
d = dict(
(key, value)
for key, value in inspect.getmembers(obj)
if not key.startswith("__")
and not inspect.isabstract(value)
and not inspect.isbuiltin(value)
and not inspect.isfunction(value)
and not inspect.isgenerator(value)
and not inspect.isgeneratorfunction(value)
and not inspect.ismethod(value)
and not inspect.ismethoddescriptor(value)
and not inspect.isroutine(value)
)
return self.default(d)
return obj
Ejemplo:
class C(object):
c = "NO"
def to_json(self):
return {"c": "YES"}
class B(object):
b = "B"
i = "I"
def __init__(self, y):
self.y = y
def f(self):
print "f"
class A(B):
a = "A"
def __init__(self):
self.b = [{"ab": B("y")}]
self.c = C()
print json.dumps(A(), cls=ObjectEncoder, indent=2, sort_keys=True)
Resultado:
{
"a": "A",
"b": [
{
"ab": {
"b": "B",
"i": "I",
"y": "y"
}
}
],
"c": {
"c": "YES"
},
"i": "I"
}
Esta clase puede hacer el truco, convierte el objeto a json estándar.
import json
class Serializer(object):
@staticmethod
def serialize(object):
return json.dumps(object, default=lambda o: o.__dict__.values()[0])
uso:
Serializer.serialize(my_object)
trabajando en python2.7
y python3
.
Esta es una pequeña biblioteca que serializa un objeto con todos sus hijos a JSON y también lo analiza:
Hay muchos enfoques para este problema. ''ObjDict'' (pip install objdict) es otro. Hay un énfasis en proporcionar objetos como javascript que también pueden actuar como diccionarios para manejar mejor los datos cargados desde JSON, pero hay otras características que también pueden ser útiles. Esto proporciona otra solución alternativa al problema original.
La mayoría de las respuestas implican cambiar la llamada a json.dumps () , lo que no siempre es posible o deseable (por ejemplo, puede ocurrir dentro de un componente del marco).
Si desea poder llamar a json.dumps (obj) como está, entonces una solución simple es heredar de dict :
class FileItem(dict):
def __init__(self, fname):
dict.__init__(self, fname=fname)
f = FileItem(''tasks.txt'')
json.dumps(f) #No need to change anything here
Esto funciona si su clase es solo una representación básica de datos, para cosas más complicadas, siempre puede establecer claves explícitamente.
Me encontré con este problema cuando intenté almacenar el modelo de Peewee en PostgreSQL JSONField
.
Después de luchar por un tiempo, aquí está la solución general.
La clave de mi solución es revisar el código fuente de Python y darse cuenta de que la documentación del código (que se describe here ) ya explica cómo extender los json.dumps
existentes para admitir otros tipos de datos.
Supongamos que actualmente tiene un modelo que contiene algunos campos que no son serializables a JSON y el modelo que contiene el campo JSON originalmente tiene este aspecto:
class SomeClass(Model):
json_field = JSONField()
Solo define un JSONEncoder
personalizado como este:
class CustomJsonEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, SomeTypeUnsupportedByJsonDumps):
return < whatever value you want >
return json.JSONEncoder.default(self, obj)
@staticmethod
def json_dumper(obj):
return json.dumps(obj, cls=CustomJsonEncoder)
Y luego solo JSONField
en su JSONField
como a continuación:
class SomeClass(Model):
json_field = JSONField(dumps=CustomJsonEncoder.json_dumper)
La clave es el método default(self, obj)
anterior. Para cada ... is not JSON serializable
queja ... is not JSON serializable
que reciba de Python, solo agregue código para manejar el tipo unserializable-to-JSON (como Enum
o datetime
)
Por ejemplo, así es como apoyo una clase heredada de Enum
:
class TransactionType(Enum):
CURRENT = 1
STACKED = 2
def default(self, obj):
if isinstance(obj, TransactionType):
return obj.value
return json.JSONEncoder.default(self, obj)
Finalmente, con el código implementado como anteriormente, puedes convertir cualquier modelo de Peewee para que sea un objeto JSON-seriable como a continuación:
peewee_model = WhateverPeeweeModel()
new_model = SomeClass()
new_model.json_field = model_to_dict(peewee_model)
Aunque el código anterior era (algo) específico de Peewee, pero creo que:
- Es aplicable a otros ORMs (Django, etc.) en general
- Además, si comprendió cómo funciona
json.dumps
, esta solución también funciona con Python (sin ORM) en general
Cualquier duda, por favor publicar en la sección de comentarios. ¡Gracias!
Me gustó más el método de Lost Koder. Me encontré con problemas al intentar serializar objetos más complejos cuyos miembros / métodos no son serializables. Aquí está mi implementación que funciona en más objetos:
class Serializer(object):
@staticmethod
def serialize(obj):
def check(o):
for k, v in o.__dict__.items():
try:
_ = json.dumps(v)
o.__dict__[k] = v
except TypeError:
o.__dict__[k] = str(v)
return o
return json.dumps(check(obj).__dict__, indent=2)
Me gusta la respuesta de Onur pero me expandiría para incluir un toJSON()
opcional para que los objetos se serialicen a sí mismos:
def dumper(obj):
try:
return obj.toJSON()
except:
return obj.__dict__
print json.dumps(some_big_object, default=dumper, indent=2)
Otra opción es envolver el volcado JSON en su propia clase:
import json
class FileItem:
def __init__(self, fname):
self.fname = fname
def __repr__(self):
return json.dumps(self.__dict__)
O, mejor aún, subclasificar la clase FileItem de una clase JsonSerializable
:
import json
class JsonSerializable(object):
def toJson(self):
return json.dumps(self.__dict__)
def __repr__(self):
return self.toJson()
class FileItem(JsonSerializable):
def __init__(self, fname):
self.fname = fname
Pruebas:
>>> f = FileItem(''/foo/bar'')
>>> f.toJson()
''{"fname": "/foo/bar"}''
>>> f
''{"fname": "/foo/bar"}''
>>> str(f) # string coercion
''{"fname": "/foo/bar"}''
Para clases más complejas usted podría considerar la herramienta jsonpickle :
jsonpickle es una biblioteca de Python para la serialización y deserialización de objetos Python complejos desde y hacia JSON.
Las bibliotecas estándar de Python para codificar Python en JSON, como json, simplejson y demjson de stdlib, solo pueden manejar primitivas de Python que tienen un equivalente directo de JSON (por ejemplo, dicts, listas, cadenas, intts, etc.). jsonpickle se basa en estas bibliotecas y permite que las estructuras de datos más complejas se serialicen a JSON. jsonpickle es altamente configurable y ampliable, lo que permite al usuario elegir el backend de JSON y agregar backends adicionales.
Se me ocurrió mi propia solución. Utilice este método, pase cualquier documento ( dict , lista , ObjectId , etc.) para serializar.
def getSerializable(doc):
# check if it''s a list
if isinstance(doc, list):
for i, val in enumerate(doc):
doc[i] = getSerializable(doc[i])
return doc
# check if it''s a dict
if isinstance(doc, dict):
for key in doc.keys():
doc[key] = getSerializable(doc[key])
return doc
# Process ObjectId
if isinstance(doc, ObjectId):
doc = str(doc)
return doc
# Use any other custom serializting stuff here...
# For the rest of stuff
return doc
Si no le importa instalar un paquete para él, puede usar json-tricks :
pip install json-tricks
Después de eso, solo necesitas importar los dump(s)
de json_tricks
lugar de json, y normalmente funcionará:
from json_tricks import dumps
json_str = dumps(cls_instance, indent=4)
que daré
{
"__instance_type__": [
"module_name.test_class",
"MyTestCls"
],
"attributes": {
"attr": "val",
"dct_attr": {
"hello": 42
}
}
}
¡Y eso es básicamente!
Esto funcionará muy bien en general. Hay algunas excepciones, por ejemplo, si ocurren cosas especiales en __new__
, o si se está produciendo más metaclase mágica.
Obviamente, cargar también funciona (de lo contrario, ¿cuál es el punto)?
from json_tricks import loads
json_str = loads(json_str)
Esto asume que module_name.test_class.MyTestCls
puede ser importado y no ha cambiado en formas no compatibles. Obtendrá una instancia , no un diccionario o algo así, y debería ser una copia idéntica a la que descargó.
Si desea personalizar la forma en que se desordena (serializa) algo, puede agregar métodos especiales a su clase, como por ejemplo:
class CustomEncodeCls:
def __init__(self):
self.relevant = 42
self.irrelevant = 37
def __json_encode__(self):
# should return primitive, serializable types like dict, list, int, string, float...
return {''relevant'': self.relevant}
def __json_decode__(self, **attrs):
# should initialize all properties; note that __init__ is not called implicitly
self.relevant = attrs[''relevant'']
self.irrelevant = 12
que serializa solo parte de los parámetros de atributos, como ejemplo.
Y como bonificación gratuita, obtiene (des) serialización de arrays numpy, fecha y hora, mapas ordenados, así como la posibilidad de incluir comentarios en json.
Descargo de responsabilidad: creé json_tricks , porque tuve el mismo problema que tú.
Solo agrega el método to_json
a tu clase de esta manera:
def to_json(self):
return self.message # or how you want it to be serialized
Y agrega este código (de esta respuesta ) , a algún lugar en la parte superior de todo:
from json import JSONEncoder
def _default(self, obj):
return getattr(obj.__class__, "to_json", _default.default)(obj)
_default.default = JSONEncoder().default
JSONEncoder.default = _default
Este será el módulo json de parche de mono cuando se importa, por lo que JSONEncoder.default () busca automáticamente un método especial "to_json ()" y lo utiliza para codificar el objeto si lo encuentra.
Al igual que Onur dijo, pero esta vez no tiene que actualizar cada json.dumps()
en su proyecto.
jsonweb parece ser la mejor solución para mí. Ver http://www.jsonweb.info/en/latest/
from jsonweb.encode import to_object, dumper
@to_object()
class DataModel(object):
def __init__(self, id, value):
self.id = id
self.value = value
>>> data = DataModel(5, "foo")
>>> dumper(data)
''{"__type__": "DataModel", "id": 5, "value": "foo"}''
jaraco dio una respuesta bastante ordenada. Necesitaba arreglar algunas cosas menores, pero esto funciona:
Código
# Your custom class
class MyCustom(object):
def __json__(self):
return {
''a'': self.a,
''b'': self.b,
''__python__'': ''mymodule.submodule:MyCustom.from_json'',
}
to_json = __json__ # supported by simplejson
@classmethod
def from_json(cls, json):
obj = cls()
obj.a = json[''a'']
obj.b = json[''b'']
return obj
# Dumping and loading
import simplejson
obj = MyCustom()
obj.a = 3
obj.b = 4
json = simplejson.dumps(obj, for_json=True)
# Two-step loading
obj2_dict = simplejson.loads(json)
obj2 = MyCustom.from_json(obj2_dict)
# Make sure we have the correct thing
assert isinstance(obj2, MyCustom)
assert obj2.__dict__ == obj.__dict__
Tenga en cuenta que necesitamos dos pasos para cargar. Por ahora, la propiedad __python__
no se usa.
¿Qué tan común es esto?
Usando el método de AlJohri , AlJohri popularidad de los enfoques:
Serialización (Python -> JSON):
-
to_json
: 266,595 en 2018-06-27 - aJSON: 96,307 en 2018-06-27
-
__json__
: 8,504 en 2018-06-27 -
for_json
: 6,937 en 2018-06-27
Deserialización (JSON -> Python):
-
from_json
: 226,101 en 2018-06-27
json
está limitado en cuanto a los objetos que puede imprimir, y jsonpickle
(puede que necesite un pip install jsonpickle
) está limitado en cuanto a que no puede sangrar texto. Si desea inspeccionar el contenido de un objeto cuya clase no puede cambiar, todavía no puedo encontrar una manera más recta que:
import json
import jsonpickle
...
print json.dumps(json.loads(jsonpickle.encode(object)), indent=2)
Tenga en cuenta que todavía no pueden imprimir los métodos de objeto.
import json
class Foo(object):
def __init__(self):
self.bar = ''baz''
self._qux = ''flub''
def somemethod(self):
pass
def default(instance):
return {k: v
for k, v in vars(instance).items()
if not str(k).startswith(''_'')}
json_foo = json.dumps(Foo(), default=default)
assert ''{"bar": "baz"}'' == json_foo
print(json_foo)
import simplejson
class User(object):
def __init__(self, name, mail):
self.name = name
self.mail = mail
def _asdict(self):
return self.__dict__
print(simplejson.dumps(User(''alice'', ''[email protected]'')))
Si usa json
estándar, necesita definir una función default
import json
def default(o):
return o._asdict()
print(json.dumps(User(''alice'', ''[email protected]''), default=default))