A serializar a instância de classe para JSON

estou a tentar criar uma representação de cadeia JSON de uma instância de classe e a ter dificuldade. Digamos que a classe é assim:

class testclass:
    value1 = "a"
    value2 = "b"
Uma chamada para o json.as lixeiras são feitas assim:

t = testclass()
json.dumps(t)
Está a falhar e a dizer-me que a prova não é serializável pelo JSON.

TypeError: <__main__.testclass object at 0x000000000227A400> is not JSON serializable

também tentei usar o módulo de pickles:

t = testclass()
print(pickle.dumps(t, pickle.HIGHEST_PROTOCOL))

e dá informações de instância de classe, mas não um conteúdo serializado da instância de classe.

b'\x80\x03c__main__\ntestclass\nq\x00)\x81q\x01}q\x02b.'
O que foi? estou a fazer mal?

Author: martineau, 2012-04-20

10 answers

O problema básico é que o codificador JSON json.dumps() só sabe como serializar um conjunto limitado de tipos de objectos por omissão, todos os tipos incorporados. Lista aqui: https://docs.python.org/3.3/library/json.html#encoders-and-decoders

Uma boa solução seria fazer a sua classe herdar de JSONEncoder e depois implementar a função JSONEncoder.default(), e fazer com que essa função emitisse o JSON correcto para a sua classe.

Uma solução simples seria ligar json.dumps() para o membro de .__dict__ aquele caso. Esse é um Python padrão dict e se sua classe é simples, ele será serializável JSON.

class Foo(object):
    def __init__(self):
        self.x = 1
        self.y = 2

foo = Foo()
s = json.dumps(foo) # raises TypeError with "is not JSON serializable"

s = json.dumps(foo.__dict__) # s set to: {"x":1, "y":2}

A abordagem acima é discutida neste Blog postando:

Serializar objectos em Python arbitrários para JSON usando _ _ dict__

Nota: eu editei esta resposta; a versão original só discutiu a abordagem de serialização .__dict__.

 146
Author: steveha, 2018-01-30 01:55:19
Há uma maneira que funciona muito bem para mim que podes experimentar:

json.dumps() pode obter um parâmetro opcional por omissão onde poderá indicar uma função de serialização personalizada para tipos desconhecidos, a qual, no meu caso, se parece com

def serialize(obj):
    """JSON serializer for objects not serializable by default json code"""

    if isinstance(obj, date):
        serial = obj.isoformat()
        return serial

    if isinstance(obj, time):
        serial = obj.isoformat()
        return serial

    return obj.__dict__

Os dois primeiros ifs são para a serialização da data e da hora. e depois há um obj.__dict__ devolvido para qualquer outro objecto.

A última chamada parece:
json.dumps(myObj, default=serialize)
É especialmente bom quando se está a serializar uma colecção e não se quer chamar __dict__ explicitamente para cada objecto. Aqui está feito para ti automaticamente. Até agora, funcionou tão bem para mim, à espera dos teus pensamentos.
 25
Author: Broccoli, 2016-12-17 16:25:13

Eu apenas faço:

data=json.dumps(myobject.__dict__)
Esta não é a resposta completa, e se você tiver algum tipo de classe de objetos complicados você certamente não terá tudo. No entanto, uso isto para alguns dos meus objectos simples.

Um que funciona muito bem é a classe "opções" que você obtém a partir do módulo OptionParser. Aqui está junto com o próprio pedido JSON.

  def executeJson(self, url, options):
        data=json.dumps(options.__dict__)
        if options.verbose:
            print data
        headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
        return requests.post(url, data, headers=headers)
 19
Author: SpiRail, 2013-07-10 14:36:02

Pode indicar o parâmetro default nomeado na função json.dumps():

json.dumps(obj, default=lambda x: x.__dict__)

Explicação:

Formem os documentos.(2.7, 3.6):

``default(obj)`` is a function that should return a serializable version
of obj or raise TypeError. The default simply raises TypeError.

(funciona em Python 2.7 e Python 3.x)

Nota: neste caso você precisa de instance variáveis e não class variáveis, como o exemplo na pergunta tenta fazer. (Estou assumindo que o asker quis dizer class instance ser um objeto de uma classe)

Aprendi isto primeiro com a resposta de @phihag Aqui. Encontrei-o seja a maneira mais simples e limpa de fazer o trabalho.

 12
Author: codeman48, 2017-06-27 10:28:59

Utilizar jsonpickle

import jsonpickle

object = YourClass()
json_object = jsonpickle.encode(object)
 5
Author: gies0r, 2017-03-05 18:02:44

O JSON não se destina a serializar objectos arbitrários em Python. É ótimo para serializar objetos {[[0]}, mas o módulo pickle é realmente o que você deveria estar usando em geral. A saída de pickle não é realmente legível pelo homem, mas deve ser implicável perfeitamente. Se você insistir em usar o JSON, você pode verificar o Módulo jsonpickle, que é uma abordagem híbrida interessante.

Https://github.com/jsonpickle/jsonpickle

 3
Author: Brendan Wood, 2012-04-20 19:13:39
Aqui estão duas funções simples para serialização de qualquer classe não sofisticada, nada extravagante como explicado antes.

Uso isto para o tipo de configuração porque posso adicionar novos membros às aulas sem ajustes de código.

import json

class SimpleClass:
    def __init__(self, a=None, b=None, c=None):
        self.a = a
        self.b = b
        self.c = c

def serialize_json(instance=None, path=None):
    dt = {}
    dt.update(vars(instance))

    with open(path, "w") as file:
        json.dump(dt, file)

def deserialize_json(cls=None, path=None):
    def read_json(_path):
        with open(_path, "r") as file:
            return json.load(file)

    data = read_json(path)

    instance = object.__new__(cls)

    for key, value in data.items():
        setattr(instance, key, value)

    return instance

# Usage: Create class and serialize under Windows file system.
write_settings = SimpleClass(a=1, b=2, c=3)
serialize_json(write_settings, r"c:\temp\test.json")

# Read back and rehydrate.
read_settings = deserialize_json(SimpleClass, r"c:\temp\test.json")

# results are the same.
print(vars(write_settings))
print(vars(read_settings))

# output:
# {'c': 3, 'b': 2, 'a': 1}
# {'c': 3, 'b': 2, 'a': 1}
 2
Author: GBGOLC, 2017-10-10 17:23:07
Creio que em vez de herança, como sugerido na resposta aceite, é melhor usar o polimorfismo. Caso contrário, você tem que ter uma grande se outra declaração para personalizar a codificação de cada objeto. Isso significa criar um codificador padrão genérico para JSON como:
def jsonDefEncoder(obj):
   if hasattr(obj, 'jsonEnc'):
      return obj.jsonEnc()
   else: #some default behavior
      return obj.__dict__

E depois ter uma função jsonEnc() em cada classe que você quer serializar. por exemplo

class A(object):
   def __init__(self,lengthInFeet):
      self.lengthInFeet=lengthInFeet
   def jsonEnc(self):
      return {'lengthInMeters': lengthInFeet * 0.3 } # each foot is 0.3 meter
Então liga-me.json.dumps(classInstance,default=jsonDefEncoder)
 1
Author: hwat, 2017-01-04 21:44:00

Python3.x

A melhor abordagem que consegui alcançar com o meu conhecimento foi esta.
Note que este código tratar conjunto() também.
Esta abordagem é genérica apenas precisando da extensão da classe (no segundo exemplo).
Note que eu estou apenas fazendo isso para arquivos, mas é fácil de modificar o comportamento ao seu gosto. No entanto, isto é um CoDec. Com um pouco mais de trabalho você pode construir sua classe de outras maneiras. Eu assumo que um construtor padrão para a instância, então eu atualizo o dict da classe.
import json
import collections


class JsonClassSerializable(json.JSONEncoder):

    REGISTERED_CLASS = {}

    def register(ctype):
        JsonClassSerializable.REGISTERED_CLASS[ctype.__name__] = ctype

    def default(self, obj):
        if isinstance(obj, collections.Set):
            return dict(_set_object=list(obj))
        if isinstance(obj, JsonClassSerializable):
            jclass = {}
            jclass["name"] = type(obj).__name__
            jclass["dict"] = obj.__dict__
            return dict(_class_object=jclass)
        else:
            return json.JSONEncoder.default(self, obj)

    def json_to_class(self, dct):
        if '_set_object' in dct:
            return set(dct['_set_object'])
        elif '_class_object' in dct:
            cclass = dct['_class_object']
            cclass_name = cclass["name"]
            if cclass_name not in self.REGISTERED_CLASS:
                raise RuntimeError(
                    "Class {} not registered in JSON Parser"
                    .format(cclass["name"])
                )
            instance = self.REGISTERED_CLASS[cclass_name]()
            instance.__dict__ = cclass["dict"]
            return instance
        return dct

    def encode_(self, file):
        with open(file, 'w') as outfile:
            json.dump(
                self.__dict__, outfile,
                cls=JsonClassSerializable,
                indent=4,
                sort_keys=True
            )

    def decode_(self, file):
        try:
            with open(file, 'r') as infile:
                self.__dict__ = json.load(
                    infile,
                    object_hook=self.json_to_class
                )
        except FileNotFoundError:
            print("Persistence load failed "
                  "'{}' do not exists".format(file)
                  )


class C(JsonClassSerializable):

    def __init__(self):
        self.mill = "s"


JsonClassSerializable.register(C)


class B(JsonClassSerializable):

    def __init__(self):
        self.a = 1230
        self.c = C()


JsonClassSerializable.register(B)


class A(JsonClassSerializable):

    def __init__(self):
        self.a = 1
        self.b = {1, 2}
        self.c = B()

JsonClassSerializable.register(A)

A().encode_("test")
b = A()
b.decode_("test")
print(b.a)
print(b.b)
print(b.c.a)

Editar

Com um pouco mais de pesquisa, encontrei uma forma de generalizar sem a necessidade da chamada de método de Registo superclass, usando um metaclass

import json
import collections

REGISTERED_CLASS = {}

class MetaSerializable(type):

    def __call__(cls, *args, **kwargs):
        if cls.__name__ not in REGISTERED_CLASS:
            REGISTERED_CLASS[cls.__name__] = cls
        return super(MetaSerializable, cls).__call__(*args, **kwargs)


class JsonClassSerializable(json.JSONEncoder, metaclass=MetaSerializable):

    def default(self, obj):
        if isinstance(obj, collections.Set):
            return dict(_set_object=list(obj))
        if isinstance(obj, JsonClassSerializable):
            jclass = {}
            jclass["name"] = type(obj).__name__
            jclass["dict"] = obj.__dict__
            return dict(_class_object=jclass)
        else:
            return json.JSONEncoder.default(self, obj)

    def json_to_class(self, dct):
        if '_set_object' in dct:
            return set(dct['_set_object'])
        elif '_class_object' in dct:
            cclass = dct['_class_object']
            cclass_name = cclass["name"]
            if cclass_name not in REGISTERED_CLASS:
                raise RuntimeError(
                    "Class {} not registered in JSON Parser"
                    .format(cclass["name"])
                )
            instance = REGISTERED_CLASS[cclass_name]()
            instance.__dict__ = cclass["dict"]
            return instance
        return dct

    def encode_(self, file):
        with open(file, 'w') as outfile:
            json.dump(
                self.__dict__, outfile,
                cls=JsonClassSerializable,
                indent=4,
                sort_keys=True
            )

    def decode_(self, file):
        try:
            with open(file, 'r') as infile:
                self.__dict__ = json.load(
                    infile,
                    object_hook=self.json_to_class
                )
        except FileNotFoundError:
            print("Persistence load failed "
                  "'{}' do not exists".format(file)
                  )


class C(JsonClassSerializable):

    def __init__(self):
        self.mill = "s"


class B(JsonClassSerializable):

    def __init__(self):
        self.a = 1230
        self.c = C()


class A(JsonClassSerializable):

    def __init__(self):
        self.a = 1
        self.b = {1, 2}
        self.c = B()


A().encode_("test")
b = A()
b.decode_("test")
print(b.a)
# 1
print(b.b)
# {1, 2}
print(b.c.a)
# 1230
print(b.c.c.mill)
# s
 1
Author: Davi Abreu Wasserberg, 2018-08-19 05:48:37
Há algumas boas respostas sobre como começar a fazer isto. Mas há algumas coisas a ter em mente:
    E se a instância estiver aninhada dentro de uma grande estrutura de dados? E se também quiser o nome da classe? E se quiseres desertar a instância? E se estiver a usar __slots__ em vez de __dict__? E se não o quiseres fazer sozinho?

Json-tricks é uma biblioteca (que eu fiz e outros contribuiu para) que tem sido capaz de fazer isso por um bom tempo. Por exemplo:

class MyTestCls:
    def __init__(self, **kwargs):
        for k, v in kwargs.items():
            setattr(self, k, v)

cls_instance = MyTestCls(s='ub', dct={'7': 7})

json = dumps(cls_instance, indent=4)
instance = loads(json)
Vais recuperar a tua instância. Aqui o json parece assim:
{
    "__instance_type__": [
        "json_tricks.test_class",
        "MyTestCls"
    ],
    "attributes": {
        "s": "ub",
        "dct": {
            "7": 7
        }
    }
}

Se você gosta de fazer a sua própria solução, você pode olhar para a fonte de json-tricks de modo a não esquecer alguns casos especiais (como __slots__).

Ele também faz outros tipos como matrizes numpy, datetimes, números complexos; ele também permite comentários.

 0
Author: Mark, 2017-09-19 18:11:12