Cómo hacer que una clase JSON sea serializable

834

¿Cómo hacer que una clase de Python sea serializable?

Una clase simple:

class FileItem:
    def __init__(self, fname):
        self.fname = fname

¿Qué debo hacer para poder obtener resultados de:

>>> import json

>>> my_file = FileItem('/foo/bar')
>>> json.dumps(my_file)
TypeError: Object of type 'FileItem' is not JSON serializable

Sin el error

Sergey
fuente
31
Es lamentable que todas las respuestas parecen responder a la pregunta "¿Cómo serializo una clase?" en lugar de la pregunta de acción "¿Cómo hago que una clase sea serializable?" Estas respuestas suponen que usted está haciendo la serialización usted mismo, en lugar de pasar el objeto a otro módulo que lo serializa.
Kyle Delaney
Si está usando Python3.5 +, podría usar jsons. Convertirá su objeto (y todos sus atributos de forma recursiva ) en un dict. import jsonsvea la respuesta a continuación - funciona perfectamente bien
tswaehn

Respuestas:

551

¿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 json.dumps(f.__dict__).

Si desea una salida más personalizada, tendrá que subclasificar JSONEncodere implementar su propia serialización personalizada.

Para un ejemplo trivial, ver abajo.

>>> from json import JSONEncoder
>>> class MyEncoder(JSONEncoder):
        def default(self, o):
            return o.__dict__    

>>> MyEncoder().encode(f)
'{"fname": "/foo/bar"}'

Luego pasa esta clase al json.dumps()método como clskwarg:

json.dumps(cls=MyEncoder)

Si también desea decodificar, deberá proporcionar una costumbre object_hooka la JSONDecoderclase. Por ej.

>>> 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>
>>> 
Manoj Govindan
fuente
44
Usar __dict__no funcionará en todos los casos. Si los atributos no se han establecido después de la instancia del objeto, es __dict__posible que no se completen por completo. En el ejemplo anterior, está bien, pero si tiene atributos de clase que también desea codificar, no se enumerarán a __dict__menos que se hayan modificado en la __init__llamada de la clase o de alguna otra manera después de que se haya instanciado el objeto.
Kris Hardy
8
+1, pero la from_json()función utilizada como enlace de objeto debe tener una else: return json_objectdeclaración, por lo que también puede tratar con objetos generales.
jogojapan
8
@KrisHardy __dict__tampoco funciona si lo usa __slots__en una nueva clase de estilo.
badp
77
Puede usar una costumbre JSONEncodercomo la anterior para crear un protocolo personalizado, como verificar la existencia del __json_serializable__método y llamarlo para obtener una representación serializable JSON del objeto. Esto estaría en consonancia con otros patrones de Python, como __getitem__, __str__, __eq__, y __len__.
jpmc26
55
__dict__tampoco funcionará recursivamente, por ejemplo, si un atributo de su objeto es otro objeto.
Neel
635

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)

Entonces solo lo llamas para serializar:

me = Object()
me.name = "Onur"
me.age = 35
me.dog = Object()
me.dog.name = "Apollo"

print(me.toJSON())

dará salida:

{
    "age": 35,
    "dog": {
        "name": "Apollo"
    },
    "name": "Onur"
}
Onur Yıldırım
fuente
82
Muy limitado. Si tiene un dict {"foo": "bar", "baz": "bat"}, se serializará en JSON fácilmente. Si en cambio tienes {"foo": "bar", "baz": MyObject ()}, entonces no puedes. La situación ideal sería que los objetos anidados se serialicen en JSON de forma recursiva, no explícitamente.
Mark E. Haase
30
Seguirá funcionando. Que se está perdiendo o.__dict___. Pruebe su propio ejemplo: class MyObject(): def __init__(self): self.prop = 1 j = json.dumps({ "foo": "bar", "baz": MyObject() }, default=lambda o: o.__dict__)
Onur Yıldırım
14
¿Es esta solución reversible? Es decir, ¿es fácil reconstruir el objeto desde json?
Jorge Leitao
2
@ JCLeitão No. Podría tener dos clases diferentes con los mismos campos. Los objetos ayb de esa clase (probablemente con las mismas propiedades) tendrían el mismo a.__dict__/ b.__dict__.
Martin Thoma
77
Esto no funciona con datetime.datetimeinstancias. Lanza el siguiente error:'datetime.datetime' object has no attribute '__dict__'
Bruno Finger
171

Para clases más complejas, puede considerar la herramienta jsonpickle :

jsonpickle es una biblioteca de Python para la serialización y deserialización de objetos complejos de Python hacia y desde JSON.

Las bibliotecas estándar de Python para codificar Python en JSON, como jd, simplejson y demjson de stdlib, solo pueden manejar primitivas de Python que tienen un equivalente JSON directo (por ejemplo, dictos, listas, cadenas, ints, etc.). jsonpickle se basa en estas bibliotecas y permite serializar estructuras de datos más complejas en JSON. jsonpickle es altamente configurable y extensible, lo que permite al usuario elegir el backend JSON y agregar backends adicionales.

(enlace a jsonpickle en PyPi)

gecco
fuente
32
Viniendo de C #, esto es lo que esperaba. Un trazador de líneas simple y sin problemas con las clases.
Jerther
2
jsonpickle es asombroso. Funcionó perfectamente para un objeto enorme, complejo y desordenado con muchos niveles de clases
wisbucky
¿Hay algún ejemplo de la forma correcta de guardar esto en un archivo? La documentación solo muestra cómo codificar y decodificar un jsonpickleobjeto. Además, esto no fue capaz de decodificar un dict de dictos que contienen marcos de datos de pandas.
user5359531
3
@ user5359531 puede usar obj = jsonpickle.decode(file.read())y file.write(jsonpickle.encode(obj)).
Kilian Batzner
1
Una pregunta específica para django: ¿el uso de jsonpickle para serializar datos de sesión tiene la misma vulnerabilidad que pickle? (como se describe aquí docs.djangoproject.com/en/1.11/topics/http/sessions/… )?
Paul Bormans
90

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 de 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 de datos básica, para cosas más difíciles siempre puede establecer claves explícitamente.

andyhasit
fuente
2
Esto realmente puede ser una buena solución :) Creo que para mi caso lo es. Beneficios: comunicas la "forma" del objeto convirtiéndolo en una clase con init, es inherentemente serializable y parece interpretable como repr .
PascalVKooten
1
Aunque todavía falta "dot-access" :(
PascalVKooten
2
Ahh eso parece funcionar! Gracias, no estoy seguro de por qué esta no es la respuesta aceptada. Estoy totalmente de acuerdo en que cambiarlo dumpsno es una buena solución. Por cierto, en la mayoría de los casos, probablemente desee tener dictherencia junto con delegación, lo que significa que tendrá algún dictatributo de tipo dentro de su clase, luego pasará este atributo como parámetro como inicialización super().__init__(self.elements).
cglacet
47

Me gusta la respuesta de Onur, pero se expandiría para incluir un toJSON()método opcional para que los objetos se serialicen:

def dumper(obj):
    try:
        return obj.toJSON()
    except:
        return obj.__dict__
print json.dumps(some_big_object, default=dumper, indent=2)
Jason S
fuente
Encontré que este es el mejor equilibrio entre usar el json.dumpsmanejo personalizado existente e introducirlo. ¡Gracias!
Daniel Buckmaster
12
En realidad esto me gusta; pero en lugar de try-catchprobablemente haría algo como if 'toJSON' in obj.__attrs__():... para evitar una falla silenciosa (en caso de falla en to JSON () por alguna otra razón que no esté allí) ... una falla que potencialmente conduce a la corrupción de datos.
thclark
39

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 JsonSerializableclase:

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"}'
Paulo Freitas
fuente
2
Hola, no me gusta mucho este enfoque de "codificador personalizado", sería mejor si puedes hacer que tu clase json sea seriazable. Lo intento, y trato y trato y nada. ¿Hay alguna idea de cómo hacer esto? La cuestión es que el módulo json prueba su clase contra los tipos de python integrados, e incluso dice que para las clases personalizadas haga su codificador :). ¿Se puede fingir? Entonces, ¿podría hacerle algo a mi clase para que se comporte como una simple lista para el módulo json? Intento subclasscheck y instancecheck pero nada.
Bojan Radojevic
@ADRENALIN Podría heredar de un tipo primario (probablemente dict), si todos los valores de los atributos de clase son serializables y no le importan los hacks. También puede usar jsonpickle o json_tricks o algo así en lugar del estándar (sigue siendo un codificador personalizado, pero no necesita escribir o llamar). El primero conserva la instancia, la última la almacena como un dict de atributos, que puede cambiar implementando __json__encode__/ __json_decode__(divulgación: hice el último).
Mark
30

Simplemente agregue un to_jsonmétodo a su clase como este:

def to_json(self):
  return self.message # or how you want it to be serialized

Y agregue 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

Esto hará un parche en el módulo json cuando se importe, por lo que JSONEncoder.default () automáticamente busca un método especial "to_json ()" y lo usa para codificar el objeto si se encuentra.

Tal como dijo Onur, pero esta vez no tiene que actualizar todos los elementos json.dumps()de su proyecto.

Elegante john
fuente
66
¡Muchas gracias! Esta es la única respuesta que me permite hacer lo que quiero: poder serializar un objeto sin cambiar el código existente. Los otros métodos en su mayoría no funcionan para mí. El objeto se define en una biblioteca de terceros, y el código de serialización también es de terceros. Cambiarlos será incómodo. Con tu método, solo necesito hacerlo TheObject.to_json = my_serializer.
Yongwei Wu
24

Me encontré con este problema el otro día e implementé una versión más general de un codificador para objetos 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"
}
tobigue
fuente
1
Aunque esto es un poco viejo ... Me enfrento a un error de importación circular. Entonces, en lugar de return objen la última línea, hice esto return super(ObjectEncoder, self).default(obj). Referencia AQUÍ
SomeTypeFoo
24

Si está usando Python3.5 +, podría usarlo jsons. Convertirá su objeto (y todos sus atributos de forma recursiva) en un dict.

import jsons

a_dict = jsons.dump(your_object)

O si querías una cuerda:

a_str = jsons.dumps(your_object)

O si su clase implementó jsons.JsonSerializable:

a_dict = your_object.json
RH
fuente
3
Si puede usar Python 3.7+, descubrí que la solución más limpia para convertir las clases de Python en dicts y cadenas JSON (y viceversa) es mezclar la jsonsbiblioteca con clases de datos . ¡Hasta ahora todo bien para mí!
Ruluk
3
Esta es una biblioteca externa, no integrada en la instalación estándar de Python.
Noumenon
sólo para la clase que tiene ranuras atributo
yehudahs
Puede, pero no necesita usar máquinas tragamonedas . Solo cuando descargue de acuerdo con la firma de una clase específica necesitará espacios . En la próxima versión 1.1.0, ese ya no es el caso.
RH
11
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 estándar json, necesita definir una defaultfunción

import json
def default(o):
    return o._asdict()

print(json.dumps(User('alice', '[email protected]'), default=default))
tryer3000
fuente
2
Simplifiqué esto eliminando la función _asdict con una lambda json.dumps(User('alice', '[email protected]'), default=lambda x: x.__dict__)
JustEngland
8

jsonestá limitado en términos de objetos que puede imprimir y jsonpickle(es posible que necesite a pip install jsonpickle) está limitado en términos de que no puede sangrar texto. Si desea inspeccionar el contenido de un objeto cuya clase no puede cambiar, todavía no podría encontrar una forma más directa que:

 import json
 import jsonpickle
 ...
 print  json.dumps(json.loads(jsonpickle.encode(object)), indent=2)

Nota: que todavía no pueden imprimir los métodos del objeto.

ribamar
fuente
6

Esta clase puede hacer el truco, convierte objetos 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.7y python3.

Lost Koder
fuente
Me gustó más este método. 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: `` `serializador de clase (objeto): @staticmethod def serialize (obj): def check (o): for k, v in o .__ dict __. Items (): try: _ = json .dumps (v) o .__ dict __ [k] = v excepto TypeError: o .__ dict __ [k] = str (v) return o return json.dumps (check (obj) .__ dict__, indent = 2) `` `
Will Charlton
4
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)
rectángulo
fuente
De doc : el parámetro default(obj)es una función que debería devolver una versión serializable de obj o elevar TypeError. El valor predeterminado defaultsimplemente genera TypeError.
luckydonald
4

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 __python__propiedad no se utiliza.

¿Qué tan común es esto?

Usando el método de AlJohri , verifico la popularidad de los enfoques:

Serialización (Python -> JSON):

Deserialización (JSON -> Python):

Martin Thoma
fuente
4

Esto ha funcionado bien para mí:

class JsonSerializable(object):

    def serialize(self):
        return json.dumps(self.__dict__)

    def __repr__(self):
        return self.serialize()

    @staticmethod
    def dumper(obj):
        if "serialize" in dir(obj):
            return obj.serialize()

        return obj.__dict__

y entonces

class FileItem(JsonSerializable):
    ...

y

log.debug(json.dumps(<my object>, default=JsonSerializable.dumper, indent=2))
jmhostalet
fuente
3

Si no le importa instalar un paquete, puede usar json-tricks :

pip install json-tricks

Después de eso, solo necesita importar dump(s)desde en json_trickslugar de json, y generalmente 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 todo!


Esto funcionará muy bien en general. Hay algunas excepciones, por ejemplo, si suceden cosas especiales __new__o si hay más magia de metaclase.

Obviamente, la carga también funciona (de lo contrario, cuál es el punto):

from json_tricks import loads
json_str = loads(json_str)

Esto supone que module_name.test_class.MyTestClsse puede importar y no ha cambiado de manera no compatible. Volverás a una instancia , no un diccionario o algo así, y debería ser una copia idéntica a la que dejó.

Si desea personalizar cómo se (des) serializa algo, puede agregar métodos especiales a su clase, así:

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 los atributos, como un ejemplo.

Y como un bono gratuito, obtienes (des) serialización de matrices numpy, fecha y hora, mapas ordenados, así como la capacidad de incluir comentarios en json.

Descargo de responsabilidad: creé json_tricks , porque tuve el mismo problema que tú.

marca
fuente
1
Acabo de probar json_tricks y funcionó embellecer (en 2019).
pauljohn32
2

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"}'
matthewlent
fuente
¿Funciona bien para objetos anidados? Incluyendo decodificación y codificación
Simone Zandara
1

Aquí están mis 3 centavos ...
Esto demuestra la serialización json explícita para un objeto python con forma de árbol.
Nota: Si realmente quisiera un código como este, podría usar la clase retorcida FilePath .

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)
Dan Brough
fuente
1

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 (descrita aquí ) ya explica cómo extender el código existente.json.dumps para admitir otros tipos de datos.

Suponga que actualmente tiene un modelo que contiene algunos campos que no son serializables para JSON y el modelo que contiene el campo JSON originalmente se ve así:

class SomeClass(Model):
    json_field = JSONField()

Simplemente defina una costumbre JSONEncodercomo esta:

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 úsalo en tu me JSONFieldgusta a continuación:

class SomeClass(Model):
    json_field = JSONField(dumps=CustomJsonEncoder.json_dumper)

La clave es el default(self, obj)método anterior. Por cada ... is not JSON serializablequeja que reciba de Python, simplemente agregue código para manejar el tipo no serializable a JSON (como Enumodatetime )

Por ejemplo, así es como apoyo una clase que hereda 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 el anterior, puede convertir cualquier modelo de Peewee para que sea un objeto JSON-seriozable como el siguiente:

peewee_model = WhateverPeeweeModel()
new_model = SomeClass()
new_model.json_field = model_to_dict(peewee_model)

Aunque el código anterior era (algo) específico para Peewee, pero creo que:

  1. Es aplicable a otros ORM (Django, etc.) en general.
  2. Además, si entendió cómo json.dumpsfunciona, esta solución también funciona con Python (sin ORM) en general.

Cualquier pregunta, publíquela en la sección de comentarios. ¡Gracias!

sivabudh
fuente
1

Esta función usa la recursión para iterar sobre cada parte del diccionario y luego llama a los métodos repr () de clases que no son tipos incorporados.

def sterilize(obj):
    object_type = type(obj)
    if isinstance(obj, dict):
        return {k: sterilize(v) for k, v in obj.items()}
    elif object_type in (list, tuple):
        return [sterilize(v) for v in obj]
    elif object_type in (str, int, bool):
        return obj
    else:
        return obj.__repr__()
Quinten Cabo
fuente
0

Se me ocurrió mi propia solución. Use este método, pase cualquier documento ( dict , list , 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
Dewsworld
fuente
0

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

John Moore
fuente
0

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)
Will Charlton
fuente
0

Si puede instalar un paquete, le recomiendo probar el eneldo , que funcionó bien para mi proyecto. Lo bueno de este paquete es que tiene la misma interfaz que pickle, por lo que si ya lo ha estado utilizando pickleen su proyecto, simplemente puede sustituirlo pordill y ver si se ejecuta el script, sin cambiar ningún código. ¡Entonces es una solución muy barata para probar!

(Anti-divulgación completa: de ninguna manera estoy afiliado y nunca he contribuido al proyecto de eneldo).

Instala el paquete:

pip install dill

Luego edite su código para importar en dilllugar de pickle:

# import pickle
import dill as pickle

Ejecute su script y vea si funciona. (Si lo hace, es posible que desee limpiar su código para que ya no esté sombreando elpickle nombre módulo!)

Algunos detalles sobre los tipos de datos que dillpueden y no pueden serializarse, desde la página del proyecto :

dill puede encurtir los siguientes tipos estándar:

none, type, bool, int, long, float, complex, str, unicode, tuple, list, dict, file, buffer, builtin, clases de estilo antiguas y nuevas, instancias de clases de estilo antiguas y nuevas, set, frozenset, array , funciones, excepciones

dill también puede encurtir tipos estándar más "exóticos":

funciones con rendimientos, funciones anidadas, lambdas, celda, método, método no vinculado, módulo, código, wrapper de método, dictproxy, descriptor de método, getsetdescriptor, memberdescriptor, wrapperdescriptor, xrange, slice, notimplemented, ellipsis, quit

dill aún no puede encurtir estos tipos estándar:

marco, generador, rastreo

thedavidmo
fuente
0

No veo ninguna mención aquí del control de versiones en serie o backcompat, por lo que publicaré mi solución que he estado usando durante un tiempo. Probablemente tengo mucho más para aprender, específicamente Java y Javascript son probablemente más maduros que yo aquí, pero aquí va

https://gist.github.com/andy-d/b7878d0044a4242c0498ed6d67fd50fe

Fletch F Fletch
fuente
0

Para agregar otra opción: puede usar el attrspaquete y el asdictmétodo.

class ObjectEncoder(JSONEncoder):
    def default(self, o):
        return attr.asdict(o)

json.dumps(objects, cls=ObjectEncoder)

y para volver a convertir

def from_json(o):
    if '_obj_name' in o:
        type_ = o['_obj_name']
        del o['_obj_name']
        return globals()[type_](**o)
    else:
        return o

data = JSONDecoder(object_hook=from_json).decode(data)

la clase se ve así

@attr.s
class Foo(object):
    x = attr.ib()
    _obj_name = attr.ib(init=False, default='Foo')
machinekoder
fuente
0

Además de la respuesta de Onur , es posible que desee tratar con el tipo de fecha y hora como se muestra a continuación.
(para manejar: el objeto 'datetime.datetime' no tiene excepción de atributo ' dict ').

def datetime_option(value):
    if isinstance(value, datetime.date):
        return value.timestamp()
    else:
        return value.__dict__

Uso:

def toJSON(self):
    return json.dumps(self, default=datetime_option, sort_keys=True, indent=4)
Mark Choi
fuente
0

Primero necesitamos hacer que nuestro objeto sea compatible con JSON, para que podamos volcarlo usando el módulo JSON estándar. Lo hice de esta manera:

def serialize(o):
    if isinstance(o, dict):
        return {k:serialize(v) for k,v in o.items()}
    if isinstance(o, list):
        return [serialize(e) for e in o]
    if isinstance(o, bytes):
        return o.decode("utf-8")
    return o
Adi Degani
fuente
0

Sobre la base de la respuesta de Quinten Cabo :

def sterilize(obj):
    if type(obj) in (str, float, int, bool, type(None)):
        return obj
    elif isinstance(obj, dict):
        return {k: sterilize(v) for k, v in obj.items()}
    elif hasattr(obj, '__iter__') and callable(obj.__iter__):
        return [sterilize(v) for v in obj]
    elif hasattr(obj, '__dict__'):
        return {k: sterilize(v) for k, v in obj.__dict__.items() if k not in ['__module__', '__dict__', '__weakref__', '__doc__']}
    else:
        return repr(obj)

Las diferencias son

  1. Funciona para cualquier iterable en lugar de justo listytuple (funciona para matrices NumPy, etc.)
  2. Funciona para tipos dinámicos (los que contienen un __dict__ ).
  3. Incluye tipos nativos floaty, Nonepor lo tanto, no se convierten en cadenas.

Lo que queda como ejercicio para el lector es manejar __slots__, clases que son iterables y tienen miembros, clases que son diccionarios y también tienen miembros, etc.

mheyman
fuente