Question Comment rendre une classe JSON sérialisable


Comment faire une classe Python sérialisable?

Une classe simple:

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

Que dois-je faire pour être en mesure de produire:

json.dumps()

Sans erreur (FileItem instance at ... is not JSON serializable)


510
2017-09-22 11:52


origine


Réponses:


Avez-vous une idée du résultat attendu? Par exemple ça fera l'affraire?

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

Dans ce cas, vous pouvez simplement appeler json.dumps(f.__dict__).

Si vous souhaitez une sortie plus personnalisée, vous devrez sous-classer JSONEncoder et implémentez votre propre sérialisation personnalisée.

Pour un exemple trivial, voir ci-dessous.

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

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

Ensuite, vous passez cette classe dans le json.dumps() méthode comme cls kwarg:

json.dumps(cls=MyEncoder)

Si vous souhaitez également décoder, vous devrez fournir une version personnalisée. object_hook au JSONDecoder classe. Par exemple

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

392
2017-09-22 12:02



Voici une solution simple pour une fonctionnalité simple:

.toJSON() Méthode

Au lieu d'une classe sérialisable JSON, implémentez une méthode sérialiseur:

import json

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

Donc, vous l'appelez simplement pour sérialiser:

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

print(me.toJSON())

va sortir:

{
    "age": 35,
    "dog": {
        "name": "Apollo"
    },
    "name": "Onur"
}

434
2018-03-21 02:26



Pour les classes plus complexes, vous pouvez envisager l'outil jsonpickle:

jsonpickle est une bibliothèque Python pour la sérialisation et la désérialisation d'objets Python complexes vers et depuis JSON.

Les bibliothèques Python standard pour coder Python en JSON, telles que json, simplejson et demjson de stdlib, ne peuvent gérer que des primitives Python qui ont un équivalent JSON direct (par exemple des dicts, des listes, des chaînes, des ints, etc.). jsonpickle s'appuie sur ces bibliothèques et permet de sérialiser des structures de données plus complexes en JSON. jsonpickle est hautement configurable et extensible, ce qui permet à l'utilisateur de choisir le backend JSON et d'ajouter des backends supplémentaires.

(jsonpickle sur PyPi)


105
2017-12-23 09:11



La plupart des réponses impliquent de changer l'appel à json.dumps (), ce qui n'est pas toujours possible ou souhaitable (cela peut arriver à l'intérieur d'un composant de framework par exemple).

Si vous voulez être en mesure d'appeler json.dumps (obj) tel quel, une solution simple hérite 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

Cela fonctionne si votre classe est juste une représentation de données de base, pour les choses plus délicates, vous pouvez toujours définir les clés explicitement.


46
2017-07-03 13:22



Une autre option consiste à encapsuler JSON dans sa propre classe:

import json

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

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

Ou, mieux encore, sous-classe classe FileItem d'un JsonSerializable classe:

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

Essai:

>>> f = FileItem('/foo/bar')
>>> f.toJson()
'{"fname": "/foo/bar"}'
>>> f
'{"fname": "/foo/bar"}'
>>> str(f) # string coercion
'{"fname": "/foo/bar"}'

30
2018-06-16 10:30



J'aime La réponse d'Onur mais étendrait pour inclure un optionnel toJSON() méthode pour que les objets se sérialisent:

def dumper(obj):
    try:
        return obj.toJSON()
    except:
        return obj.__dict__
print json.dumps(some_big_object, default=dumper, indent=2)

26
2018-01-27 16:04



J'ai rencontré ce problème l'autre jour et mis en œuvre une version plus générale d'un encodeur pour les objets Python qui peuvent gérer les objets imbriqués et champs hérités:

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

Exemple:

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)

Résultat:

{
  "a": "A", 
  "b": [
    {
      "ab": {
        "b": "B", 
        "i": "I", 
        "y": "y"
      }
    }
  ], 
  "c": {
    "c": "YES"
  }, 
  "i": "I"
}

21
2018-02-18 14:10



Ajoutez simplement to_json méthode à votre classe comme ceci:

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

Et ajoutez ce code (de cette réponse), à quelque chose au sommet de tout:

from json import JSONEncoder

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

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

Ce sera monkey-patch json module quand il est importé ainsi JSONEncoder.default () recherche automatiquement un "to_json ()" spécial méthode et l'utilise pour encoder l'objet si trouvé.

Tout comme Onur a dit, mais cette fois vous ne devez pas mettre à jour tous les json.dumps()dans votre projet.


15
2017-08-04 10:27



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', 'alice@mail.com')))

si utiliser standard json, vous devez définir un default fonction

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

print(json.dumps(User('alice', 'alice@mail.com'), default=default))

8
2018-06-17 03:17



json est limité en termes d'objets qu'il peut imprimer, et jsonpickle (Vous pourriez avoir besoin d'un pip install jsonpickle) est limité en termes qu'il ne peut pas indenter le texte. Si vous souhaitez inspecter le contenu d'un objet dont vous ne pouvez pas changer la classe, je ne peux toujours pas trouver un moyen plus droit que:

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

Notez qu'ils ne peuvent toujours pas imprimer les méthodes d'objet.


4
2018-04-04 13:41