Como tornar uma classe JSON serializável

834

Como tornar uma classe Python serializável?

Uma classe simples:

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

O que devo fazer para obter a saída de:

>>> import json

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

Sem o erro

Sergey
fonte
31
É lamentável que todas as respostas pareçam responder à pergunta "Como serializar uma classe?" em vez da pergunta de ação "Como faço para tornar uma classe serializável?" Essas respostas assumem que você está fazendo a serialização sozinho, em vez de passar o objeto para algum outro módulo que a serialize.
Kyle Delaney
Se você estiver usando o Python3.5 +, poderá usar jsons. Ele converterá seu objeto (e todos os seus atributos recursivamente ) em um ditado. import jsonsveja a resposta abaixo - ele funciona perfeitamente bem
tswaehn

Respostas:

551

Você tem uma idéia sobre o resultado esperado? Por exemplo, isso fará?

>>> f  = FileItem("/foo/bar")
>>> magic(f)
'{"fname": "/foo/bar"}'

Nesse caso, você pode simplesmente ligar json.dumps(f.__dict__).

Se você deseja uma saída mais personalizada, precisará subclassificar JSONEncodere implementar sua própria serialização personalizada.

Para um exemplo trivial, veja abaixo.

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

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

Então você passa essa classe para o json.dumps()método como clskwarg:

json.dumps(cls=MyEncoder)

Se você também quiser decodificar, precisará fornecer um costume object_hookpara a JSONDecoderclasse. Por exemplo

>>> 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
fonte
44
O uso __dict__não funcionará em todos os casos. Se os atributos não tiverem sido definidos após a instanciação do objeto, __dict__pode não ser totalmente preenchido. No exemplo acima, você está bem, mas se você tiver atributos de classe que também deseja codificar, eles não serão listados a __dict__menos que tenham sido modificados na __init__chamada da classe ou de alguma outra maneira após a instanciação do objeto.
Kris Hardy
8
+1, mas a from_json()função usada como gancho de objeto deve ter uma else: return json_objectinstrução, para que também possa lidar com objetos gerais.
jogojapan
8
O @KrisHardy __dict__também não funciona se você usar __slots__uma nova classe de estilo.
22413 badp
7
Você pode usar um costume JSONEncodercomo acima para criar um protocolo personalizado, como verificar a existência do __json_serializable__método e chamá-lo para obter uma representação serializável JSON do objeto. Isso estaria de acordo com outros padrões de Python, assim como __getitem__, __str__, __eq__, e __len__.
jpmc26
5
__dict__também não funcionará recursivamente, por exemplo, se um atributo do seu objeto for outro objeto.
Neel
635

Aqui está uma solução simples para um recurso simples:

.toJSON() Método

Em vez de uma classe serializável JSON, implemente um método serializador:

import json

class Object:
    def toJSON(self):
        return json.dumps(self, default=lambda o: o.__dict__, 
            sort_keys=True, indent=4)

Então, basta chamá-lo para serializar:

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

print(me.toJSON())

irá produzir:

{
    "age": 35,
    "dog": {
        "name": "Apollo"
    },
    "name": "Onur"
}
Onur Yıldırım
fonte
82
Muito limitado. Se você tiver um ditado {"foo": "bar", "baz": "bat"}, isso será serializado para JSON facilmente. Se você tiver {"foo": "bar", "baz": MyObject ()}, não poderá. A situação ideal seria que objetos aninhados sejam serializados para JSON recursivamente, não explicitamente.
precisa
30
Ainda vai funcionar. Você está perdendo o.__dict___. Tente seu próprio exemplo: 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
Esta solução é reversível? Ou seja, é fácil reconstruir o objeto de json?
Jorge Leitão
2
@ JCLeitão Não. Você poderia ter duas classes diferentes com os mesmos campos. Os objetos aeb dessa classe (provavelmente com as mesmas propriedades) teriam o mesmo a.__dict__/ b.__dict__.
Martin Thoma
7
Isso não funciona com datetime.datetimeinstâncias. Emite o seguinte erro:'datetime.datetime' object has no attribute '__dict__'
Bruno Finger
171

Para classes mais complexas, você pode considerar a ferramenta jsonpickle :

jsonpickle é uma biblioteca Python para serialização e desserialização de objetos Python complexos de e para JSON.

As bibliotecas padrão do Python para codificar Python no JSON, como json, simplejson e demjson do stdlib, podem lidar apenas com primitivas do Python que possuem um equivalente JSON direto (por exemplo, dict, listas, strings, ints, etc.). O jsonpickle é construído sobre essas bibliotecas e permite que estruturas de dados mais complexas sejam serializadas para JSON. O jsonpickle é altamente configurável e extensível - permitindo ao usuário escolher o back-end JSON e adicionar back-end adicionais.

(link para jsonpickle no PyPi)

gecco
fonte
32
Vindo de C #, era isso que eu esperava. Um liner simples e sem mexer com as classes.
Jerther
2
jsonpickle é incrível. Funcionou perfeitamente para uma enorme e complexa objeto, confuso com muitos níveis de classes
wisbucky
existe um exemplo da maneira correta de salvar isso em um arquivo? A documentação mostra apenas como codificar e decodificar um jsonpickleobjeto. Além disso, isso não foi capaz de decodificar um ditado de dict contendo quadros de dados de pandas.
user5359531
3
@ user5359531 você pode usar obj = jsonpickle.decode(file.read())e file.write(jsonpickle.encode(obj)).
Kilian Batzner
1
Uma pergunta específica para o django: o uso de jsonpickle para serializar dados de sessão tem a mesma vulnerabilidade que pickle? (conforme descrito aqui docs.djangoproject.com/en/1.11/topics/http/sessions/… )?
Paul Bormans
89

A maioria das respostas envolve alterar a chamada para json.dumps () , o que nem sempre é possível ou desejável (pode acontecer dentro de um componente da estrutura, por exemplo).

Se você quiser chamar json.dumps (obj) como está, uma solução simples será herdada do 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

Isso funciona se sua classe é apenas uma representação básica de dados, para coisas mais complicadas, você sempre pode definir chaves explicitamente.

andyhasit
fonte
2
Esta pode realmente ser uma boa solução :) Acredito que seja o meu caso. Benefícios: você comunica a "forma" do objeto, tornando-o uma classe com init, é inerentemente serializável e parece interpretável como repr .
PascalVKooten 22/09
1
Apesar de "ponto de acesso" ainda está faltando :(
PascalVKooten
2
Ahh, isso parece funcionar! Obrigado, não sei por que essa não é a resposta aceita. Concordo totalmente que mudar a dumpssolução não é uma boa solução. A propósito, na maioria dos casos, você provavelmente deseja ter dictherança junto com a delegação, o que significa que você terá algum dictatributo de tipo dentro da sua classe e passará esse atributo como parâmetro, como algo como inicialização super().__init__(self.elements).
Cglacet
47

Eu gosto da resposta de Onur, mas expandiria para incluir um toJSON()método opcional para objetos se serializarem:

def dumper(obj):
    try:
        return obj.toJSON()
    except:
        return obj.__dict__
print json.dumps(some_big_object, default=dumper, indent=2)
Jason S
fonte
Eu achei que esse era o melhor equilíbrio entre usar o json.dumpstratamento personalizado existente e de introdução. Obrigado!
precisa
12
Eu realmente gosto disso; mas em vez de try-catchprovavelmente faria algo como if 'toJSON' in obj.__attrs__():... para evitar uma falha silenciosa (no caso de falha no JSON () por algum outro motivo que não esteja lá) ... uma falha que potencialmente leva à corrupção de dados.
thclark
39

Outra opção é agrupar o dumping JSON em sua própria classe:

import json

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

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

Ou, melhor ainda, subclassificando a classe FileItem de uma JsonSerializableclasse:

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

Teste:

>>> f = FileItem('/foo/bar')
>>> f.toJson()
'{"fname": "/foo/bar"}'
>>> f
'{"fname": "/foo/bar"}'
>>> str(f) # string coercion
'{"fname": "/foo/bar"}'
Paulo Freitas
fonte
2
Oi, eu realmente não gosto dessa abordagem de "codificador personalizado", seria melhor se você pudesse tornar sua classe json seriazable. Eu tento, e tento e tento e nada. Existe alguma idéia de como fazer isso. O problema é que o módulo json testa sua classe em relação aos tipos python internos e até diz que, para classes personalizadas, faça seu codificador :). Pode ser falsificado? Para que eu pudesse fazer algo com a minha classe, para que se comportasse como uma lista simples para o módulo json? Eu tento subclasscheck e instancecheck mas nada.
Bojan Radojevic
@ADRENALIN Você pode herdar de um tipo primário (provavelmente ditado), se todos os valores de atributo de classe forem serializáveis ​​e você não se importar com hackers. Você também pode usar jsonpickle ou json_tricks ou algo assim, em vez do padrão (ainda é um codificador personalizado, mas não é necessário escrever ou ligar). O primeiro seleciona a instância, o último a armazena como um ditado de atributos, que você pode alterar implementando __json__encode__/ __json_decode__(divulgação: fiz a última).
Mark
30

Basta adicionar um to_jsonmétodo à sua classe como este:

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

E adicione este código ( desta resposta ) a algum lugar no topo de tudo:

from json import JSONEncoder

def _default(self, obj):
    return getattr(obj.__class__, "to_json", _default.default)(obj)

_default.default = JSONEncoder().default
JSONEncoder.default = _default

Isso fará o patch do módulo json quando for importado, e o JSONEncoder.default () verificará automaticamente um método "to_json ()" especial e o utilizará para codificar o objeto, se encontrado.

Assim como Onur disse, mas desta vez você não precisa atualizar todos os itens json.dumps()do seu projeto.

John extravagante
fonte
6
Muito obrigado! Esta é a única resposta que me permite fazer o que quero: poder serializar um objeto sem alterar o código existente. Os outros métodos principalmente não funcionam para mim. O objeto é definido em uma biblioteca de terceiros e o código de serialização também é de terceiros. Mudá-los será estranho. Com o seu método, eu só preciso fazer TheObject.to_json = my_serializer.
Yongwei Wu 11/11
24

Me deparei com esse problema outro dia e implementei uma versão mais geral de um Encoder para objetos Python que pode manipular objetos aninhados e campos herdados :

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

Exemplo:

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
fonte
1
Embora isso seja um pouco antigo ... estou enfrentando algum erro de importação circular. Então, em vez de return objna última linha, eu fiz isso return super(ObjectEncoder, self).default(obj). Referência AQUI
SomeTypeFoo 11/04
24

Se você estiver usando Python3.5 +, você pode usar jsons. Ele converterá seu objeto (e todos os seus atributos recursivamente) em um ditado.

import jsons

a_dict = jsons.dump(your_object)

Ou se você quiser uma string:

a_str = jsons.dumps(your_object)

Ou se sua classe implementou jsons.JsonSerializable:

a_dict = your_object.json
RH
fonte
3
Se você é capaz de usar o Python 3.7+, descobri que a solução mais limpa para converter classes python em dict e strings JSON (e vice-versa) é misturar a jsonsbiblioteca com classes de dados . Até agora, tudo bem para mim!
Ruluk
3
Esta é uma biblioteca externa, não incorporada na instalação padrão do Python.
Noumenon
apenas para classe que tem ranhuras atributo
yehudahs
Você pode, mas não precisa usar slots . Somente ao despejar de acordo com a assinatura de uma classe específica, você precisará de slots . Na próxima versão 1.1.0, isso também não é mais o 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]')))

se usar padrão json, você precisa definir uma defaultfunção

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

print(json.dumps(User('alice', '[email protected]'), default=default))
tryer3000
fonte
2
I Simplificado isto removendo a função _asdict com um lambda json.dumps(User('alice', '[email protected]'), default=lambda x: x.__dict__)
JustEngland
8

jsoné limitado em termos de objetos que pode imprimir e jsonpickle(você pode precisar de a pip install jsonpickle) é limitado em termos de não pode recuar texto. Se você deseja inspecionar o conteúdo de um objeto cuja classe você não pode alterar, ainda não consegui encontrar uma maneira mais direta do que:

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

Nota: ainda assim eles não podem imprimir os métodos de objeto.

ribamar
fonte
6

Esta classe pode fazer o truque, converte o objeto em json padrão.

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)

trabalhando em python2.7e python3.

Lost Koder
fonte
Eu gostei mais desse método. Encontrei problemas ao tentar serializar objetos mais complexos cujos membros / métodos não são serializáveis. Aqui está minha implementação que funciona em mais objetos: `` `` Serializador de classe (objeto): @staticmethod def serialize (obj): def check (o): para k, v em o .__ dict __. Items (): try: _ = json .dumps (v) o .__ dict __ [k] = v, exceto TypeError: o .__ dict __ [k] = str (v) retorna o retorno json.dumps (verifique (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)
rectangletangle
fonte
Do doc : O parâmetro default(obj)é uma função que deve retornar uma versão serializável de obj ou gerar TypeError. O padrão defaultsimplesmente gera TypeError.
luckydonald
4

jaraco deu uma resposta bastante clara. Eu precisava consertar algumas coisas menores, mas isso 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__

Observe que precisamos de duas etapas para carregar. Por enquanto, a __python__propriedade não é usada.

Quão comum é isso?

Usando o método de AlJohri , verifico a popularidade das abordagens:

Serialização (Python -> JSON):

Desserialização (JSON -> Python):

Martin Thoma
fonte
4

Isso funcionou bem para mim:

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__

e depois

class FileItem(JsonSerializable):
    ...

e

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

Se você não se importa em instalar um pacote, pode usar o json-tricks :

pip install json-tricks

Depois disso, você só precisa importar dump(s)de em json_tricksvez de json, e geralmente funcionará:

from json_tricks import dumps
json_str = dumps(cls_instance, indent=4)

o que vai dar

{
        "__instance_type__": [
                "module_name.test_class",
                "MyTestCls"
        ],
        "attributes": {
                "attr": "val",
                "dct_attr": {
                        "hello": 42
                }
        }
}

E é basicamente isso!


Isso funcionará muito bem em geral. Existem algumas exceções, por exemplo, se coisas especiais acontecem __new__ou mais magia de metaclasse está acontecendo.

Obviamente, o carregamento também funciona (caso contrário, qual é o objetivo):

from json_tricks import loads
json_str = loads(json_str)

Isso pressupõe que module_name.test_class.MyTestClspossa ser importado e não tenha sido alterado de maneiras incompatíveis. Você retornará uma instância , não algum dicionário ou algo assim, e deve ser uma cópia idêntica à que você despejou.

Se você deseja personalizar como algo é (des) serializado, você pode adicionar métodos especiais à sua classe, como:

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 apenas parte dos parâmetros dos atributos, como exemplo.

E como um bônus gratuito, você obtém (des) serialização de matrizes numpy, data e hora, mapas ordenados, bem como a capacidade de incluir comentários no json.

Isenção de responsabilidade: criei json_tricks , porque tive o mesmo problema que você.

Marca
fonte
1
Acabei de testar o json_tricks e funcionou embelezar (em 2019).
pauljohn32
2

jsonweb parece ser a melhor solução para mim. Veja 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
fonte
Funciona bem para objetos aninhados? Incluindo decodificação e codificação
Simone Zandara
1

Aqui estão meus 3 centavos ...
Isso demonstra serialização explícita de json para um objeto python do tipo árvore.
Nota: Se você realmente quisesse algum código como esse, poderia usar a classe FilePath distorcida .

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
fonte
1

Encontrei este problema ao tentar armazenar o modelo do Peewee no PostgreSQL JSONField.

Depois de lutar um pouco, aqui está a solução geral.

A chave da minha solução é passar pelo código-fonte do Python e perceber que a documentação do código (descrita aqui ) já explica como estender o existente json.dumpspara suportar outros tipos de dados.

Suponha que você atualmente tenha um modelo que contém alguns campos que não são serializáveis ​​para JSON e o modelo que contém o campo JSON originalmente se parece com isso:

class SomeClass(Model):
    json_field = JSONField()

Basta definir um costume JSONEncodercomo 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)

E então basta usá-lo JSONFieldcomo você abaixo:

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

A chave é o default(self, obj)método acima. Para cada ... is not JSON serializablereclamação que você recebe do Python, basta adicionar código para lidar com o tipo não serializável em JSON (como Enumoudatetime )

Por exemplo, veja como eu suporte uma classe herdada 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)

Por fim, com o código implementado como acima, você pode converter qualquer modelo Peewee em um objeto seriamente JSON, como abaixo:

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

Embora o código acima fosse (um pouco) específico para o Peewee, mas acho que:

  1. É aplicável a outros ORMs (Django, etc) em geral
  2. Além disso, se você entendeu como json.dumpsfunciona, esta solução também funciona com Python (sans ORM) em geral também

Qualquer dúvida, por favor poste na seção de comentários. Obrigado!

sivabudh
fonte
1

Essa função usa recursão para iterar todas as partes do dicionário e, em seguida, chama os métodos repr () de classes que não são do tipo interno.

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
fonte
0

Eu vim com minha própria solução. Use esse método, passe qualquer 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
fonte
0

Eu escolhi usar decoradores para resolver o problema de serialização de objetos de data e hora. Aqui está o meu 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

Ao importar o módulo acima, meus outros módulos usam json de maneira normal (sem especificar a palavra-chave padrão) para serializar dados que contêm objetos de data e hora. O código do serializador de data e hora é chamado automaticamente para json.dumps e json.dump.

John Moore
fonte
0

Eu gostei mais do método de Lost Koder. Encontrei problemas ao tentar serializar objetos mais complexos, cujos membros / métodos não são serializáveis. Aqui está minha implementação que funciona em mais 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
fonte
0

Se você é capaz de instalar um pacote, recomendo tentar o dill , que funcionou bem no meu projeto. Uma coisa boa sobre este pacote é que ele tem a mesma interface que pickle, portanto, se você já estiver usando pickleem seu projeto, você pode simplesmente substituir dille ver se o script é executado, sem alterar nenhum código. Portanto, é uma solução muito barata para tentar!

(Anti-divulgação total: não sou de forma alguma afiliado e nunca contribuí com o projeto de endro.)

Instale o pacote:

pip install dill

Em seguida, edite seu código para importar em dillvez de pickle:

# import pickle
import dill as pickle

Execute seu script e veja se funciona. (Se isso acontecer, você pode limpar seu código para não estar mais sombreando o picklenome do módulo!)

Algumas especificações sobre tipos de dados que dillpodem e não podem ser serializados, na página do projeto :

dill pode selecionar os seguintes tipos padrão:

nenhum, tipo, bool, int, long, float, complex, str, unicode, tupla, lista, dict, file, buffer, builtin, classes de estilo antigo e novo, instâncias de classes de estilo antigo e novo, conjunto, frozenset, matriz , funções, exceções

dill também pode selecionar tipos padrão mais "exóticos":

funções com rendimentos, funções aninhadas, lambdas, célula, método, unboundmethod, módulo, código, methodwrapper, dictproxy, methoddescriptor, getsetdescriptor, memberdescriptor, wrapperdescriptor, xrange, slice, não implementado, reticências, sair

dill ainda não é possível selecionar esses tipos padrão:

quadro, gerador, retorno

thedavidmo
fonte
0

Não vejo nenhuma menção aqui ao versionamento serial ou ao backcompat, por isso publicarei minha solução que venho usando há um pouco. Eu provavelmente tenho muito mais para aprender, especificamente Java e Javascript são provavelmente mais maduros do que eu aqui, mas aqui vai

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

Fletch F Fletch
fonte
0

Para adicionar outra opção: Você pode usar o attrspacote e o asdictmétodo.

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

json.dumps(objects, cls=ObjectEncoder)

e converter de volta

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)

classe se parece com isso

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

Além da resposta do Onur , você possivelmente deseja lidar com o tipo de data e hora como abaixo.
(para manipular: o objeto 'datetime.datetime' não possui exceção 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
fonte
0

Primeiro, precisamos tornar nosso objeto compatível com JSON, para que possamos despejá-lo usando o módulo JSON padrão. Eu fiz assim:

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
fonte
0

Com base Quinten Cabo de resposta :

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)

As diferenças são

  1. Funciona para qualquer iterável em vez de apenas listetuple (funciona para matrizes NumPy, etc.)
  2. Funciona para tipos dinâmicos (aqueles que contêm um __dict__ ).
  3. Inclui tipos nativos floate, Noneportanto, eles não são convertidos em string.

Deixado como exercício para o leitor é lidar com __slots__classes iteráveis ​​e com membros, classes que são dicionários e também com membros, etc.

mheyman
fonte