Comment rendre une classe JSON sérialisable


834

Comment rendre une classe Python sérialisable?

Une classe simple:

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

Que dois-je faire pour pouvoir obtenir la sortie de:

>>> import json

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

Sans l'erreur


31
Il est regrettable que toutes les réponses semblent répondre à la question "Comment puis-je sérialiser une classe?" plutôt que la question d'action "Comment puis-je rendre une classe sérialisable?" Ces réponses supposent que vous effectuez vous-même la sérialisation, plutôt que de passer l'objet à un autre module qui le sérialise.
Kyle Delaney

Si vous utilisez Python3.5 +, vous pouvez utiliser jsons. Il convertira votre objet (et tous ses attributs récursivement ) en dict. import jsonsvoir la réponse ci-dessous - cela fonctionne parfaitement bien
tswaehn

Réponses:


551

Avez-vous une idée de la sortie attendue? Par exemple, cela fera-t-il?

>>> 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 JSONEncoder- classer et implémenter 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 la json.dumps()méthode en tant que clskwarg:

json.dumps(cls=MyEncoder)

Si vous souhaitez également décoder, vous devrez fournir une personnalisation object_hookà la JSONDecoderclasse. 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>
>>> 

44
L'utilisation __dict__ne fonctionnera pas dans tous les cas. Si les attributs n'ont pas été définis après que l'objet a été instancié, ils __dict__peuvent ne pas être entièrement remplis. Dans l'exemple ci-dessus, tout va bien, mais si vous avez également des attributs de classe que vous souhaitez coder, ils ne seront pas répertoriés __dict__sauf s'ils ont été modifiés dans l' __init__appel de la classe ou d'une autre manière après l'instanciation de l'objet.
Kris Hardy

8
+1, mais la from_json()fonction utilisée comme objet-hook devrait avoir une else: return json_objectinstruction, afin qu'elle puisse également traiter des objets généraux.
jogojapan

8
@KrisHardy __dict__ne fonctionne pas non plus si vous utilisez __slots__une nouvelle classe de style.
badp

7
Vous pouvez utiliser une personnalisation JSONEncodercomme ci-dessus pour créer un protocole personnalisé, comme vérifier l'existence de la __json_serializable__méthode et l'appeler pour obtenir une représentation sérialisable JSON de l'objet. Ce serait conforme aux autres modèles de Python, comme __getitem__, __str__, __eq__, et __len__.
jpmc26

5
__dict__ne fonctionnera pas non plus de manière récursive, par exemple, si un attribut de votre objet est un autre objet.
Neel

635

Voici une solution simple pour une fonctionnalité simple:

.toJSON() Méthode

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

import json

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

Il suffit donc de l'appeler pour sérialiser:

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

print(me.toJSON())

affichera:

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

82
Très limité. Si vous avez un dict {"foo": "bar", "baz": "bat"}, celui-ci se sérialise facilement en JSON. Si à la place vous avez {"foo": "bar", "baz": MyObject ()}, alors vous ne pouvez pas. La situation idéale serait que les objets imbriqués soient sérialisés en JSON de manière récursive, pas explicitement.
Mark E. Haase

30
Cela fonctionnera toujours. Tu es absent o.__dict___. Essayez votre propre exemple: 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
Cette solution est-elle réversible? Ie est-il facile de reconstruire l'objet à partir de json?
Jorge Leitao

2
@ JCLeitão Non. Vous pouvez avoir deux classes différentes avec les mêmes champs. Les objets a et b de cette classe (probablement avec les mêmes propriétés) auraient le même a.__dict__/ b.__dict__.
Martin Thoma

7
Cela ne fonctionne pas avec les datetime.datetimeinstances. Il renvoie l'erreur suivante:'datetime.datetime' object has no attribute '__dict__'
Bruno Finger

171

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 encoder Python en JSON, telles que json, simplejson et demjson de stdlib, ne peuvent gérer que les primitives Python qui ont un équivalent JSON direct (par exemple, dicts, listes, chaînes, entiers, 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 - permettant à l'utilisateur de choisir le backend JSON et d'ajouter des backends supplémentaires.

(lien vers jsonpickle sur PyPi)


32
Venant de C #, c'est ce que j'attendais. Un simple doublure et sans déconner avec les classes.
Jerther

2
jsonpickle est génial. Cela fonctionnait parfaitement pour un objet énorme, complexe et désordonné avec de nombreux niveaux de classes
wisbucky

existe-t-il un exemple de la bonne façon de l'enregistrer dans un fichier? La documentation montre uniquement comment coder et décoder un jsonpickleobjet. En outre, cela n'a pas pu décoder un dict de dict contenant des trames de données pandas.
user5359531

3
@ user5359531 vous pouvez utiliser obj = jsonpickle.decode(file.read())et file.write(jsonpickle.encode(obj)).
Kilian Batzner

1
Une question spécifique à django: l'utilisation de jsonpickle pour la sérialisation des données de session présente-t-elle la même vulnérabilité que pickle? (comme décrit ici docs.djangoproject.com/en/1.11/topics/http/sessions/… )?
Paul Bormans

90

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 pouvoir appeler json.dumps (obj) tel quel , alors 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 n'est qu'une représentation de données de base, pour des choses plus délicates, vous pouvez toujours définir des clés de manière explicite.


2
Cela peut vraiment être une bonne solution :) Je crois que pour mon cas c'est le cas. Avantages: vous communiquez la "forme" de l'objet en en faisant une classe avec init, il est intrinsèquement sérialisable et il semble interprétable comme repr .
PascalVKooten

1
Bien que "dot-access" soit toujours manquant :(
PascalVKooten

2
Ahh ça semble marcher! Merci, je ne sais pas pourquoi ce n'est pas la réponse acceptée. Je suis tout à fait d'accord que changer la dumpsn'est pas une bonne solution. Soit dit en passant, dans la plupart des cas, vous souhaiterez probablement avoir l' dicthéritage avec la délégation, ce qui signifie que vous aurez un dictattribut de type à l'intérieur de votre classe, vous passerez ensuite cet attribut comme paramètre comme initialisation quelque chose comme super().__init__(self.elements).
cglacet

47

J'aime la réponse d'Onur, mais je développerais pour inclure une toJSON()méthode facultative permettant aux objets de se sérialiser:

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

J'ai trouvé que c'était le meilleur équilibre entre l'utilisation de la json.dumpsgestion existante et l'introduction d'une gestion personnalisée. Merci!
Daniel Buckmaster

12
J'aime vraiment vraiment ça; mais plutôt que de try-catchfaire probablement quelque chose comme if 'toJSON' in obj.__attrs__():... pour éviter un échec silencieux (en cas d'échec dans toJSON () pour une autre raison qu'il n'est pas là) ... un échec qui mène potentiellement à une corruption des données.
thclark

39

Une autre option consiste à envelopper le dumping 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-classer la classe FileItem d'une 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

Essai:

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

2
Salut, je n'aime pas vraiment cette approche "encodeur personnalisé", il serait préférable que vous puissiez rendre votre classe json seriazable. J'essaye, et j'essaye et j'essaye rien. Y a-t-il une idée de comment faire cela. Le fait est que le module json teste votre classe par rapport aux types python intégrés, et dit même que pour les classes personnalisées, faites votre encodeur :). Peut-il être truqué? Donc je pourrais faire quelque chose à ma classe pour qu'elle se comporte comme une simple liste de modules json? J'essaye subclasscheck et instancecheck mais rien.
Bojan Radojevic

@ADRENALIN Vous pouvez hériter d'un type principal (probablement dict), si toutes les valeurs d'attribut de classe sont sérialisables et que cela ne vous dérange pas de pirater. Vous pouvez également utiliser jsonpickle ou json_tricks ou quelque chose au lieu de celui standard (toujours un encodeur personnalisé, mais pas celui que vous devez écrire ou appeler). Le premier décapage l'instance, le second la stocke sous forme d'attributs, que vous pouvez modifier en implémentant __json__encode__/ __json_decode__(divulgation: j'ai fait la dernière).
Mark

30

Ajoutez simplement une to_jsonméthode à votre classe comme ceci:

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

Et ajoutez ce code (à partir de cette réponse ) , quelque part en haut 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 module json corrige les singes lors de son importation. JSONEncoder.default () recherche automatiquement une méthode spéciale "to_json ()" et l'utilise pour coder l'objet s'il est trouvé.

Tout comme Onur l'a dit, mais cette fois, vous n'avez pas à mettre à jour chaque élément json.dumps()de votre projet.


6
Grand merci! C'est la seule réponse qui me permette de faire ce que je veux: être capable de sérialiser un objet sans changer le code existant. Les autres méthodes ne fonctionnent généralement pas pour moi. L'objet est défini dans une bibliothèque tierce et le code de sérialisation est également tiers. Les changer sera gênant. Avec votre méthode, je n'ai qu'à faire TheObject.to_json = my_serializer.
Yongwei Wu

24

Je suis tombé sur ce problème l'autre jour et j'ai implémenté une version plus générale d'un encodeur pour les objets Python qui peut gérer les objets imbriqués et les 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"
}

1
Bien que ce soit un peu vieux, je suis confronté à une erreur d'importation circulaire. Donc, au lieu de return objla dernière ligne, je l'ai fait return super(ObjectEncoder, self).default(obj). Référence ICI
SomeTypeFoo

24

Si vous utilisez Python3.5 +, vous pouvez utiliser jsons. Il convertira votre objet (et tous ses attributs récursivement) en dict.

import jsons

a_dict = jsons.dump(your_object)

Ou si vous vouliez une chaîne:

a_str = jsons.dumps(your_object)

Ou si votre classe a implémenté jsons.JsonSerializable:

a_dict = your_object.json

3
Si vous êtes capable d'utiliser Python 3.7+, j'ai trouvé que la solution la plus propre pour convertir des classes python en dict et en chaînes JSON (et vice versa) est de mélanger la jsonsbibliothèque avec des classes de données . Jusqu'à présent, tant mieux pour moi!
Ruluk

3
Il s'agit d'une bibliothèque externe, non intégrée à l'installation Python standard.
Noumenon

uniquement pour la classe qui a l' attribut slots
yehudahs

Vous pouvez, mais vous n'avez pas besoin d'utiliser les slots . Ce n'est que lors du dumping selon la signature d'une classe spécifique que vous aurez besoin de slots . Dans la prochaine version 1.1.0, ce n'est plus le cas non plus.
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', 'alice@mail.com')))

si utilisation standard json, vous devez définir une defaultfonction

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

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

2
J'ai simplifié cela en supprimant la fonction _asdict avec un lambda json.dumps(User('alice', 'alice@mail.com'), default=lambda x: x.__dict__)
JustEngland

8

jsonest limité en termes d'objets qu'il peut imprimer et jsonpickle(vous en aurez peut-être besoin 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 pouvais toujours pas trouver de voie plus droite que:

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

Remarque: ils ne peuvent toujours pas imprimer les méthodes d'objet.


6

Cette classe peut faire l'affaire, elle convertit l'objet en json standard.

import json


class Serializer(object):
    @staticmethod
    def serialize(object):
        return json.dumps(object, default=lambda o: o.__dict__.values()[0])

usage:

Serializer.serialize(my_object)

travaillant dans python2.7et python3.


J'ai le plus aimé cette méthode. J'ai rencontré des problèmes lors de la tentative de sérialisation d'objets plus complexes dont les membres / méthodes ne sont pas sérialisables. Voici mon implémentation qui fonctionne sur plus d'objets: `` `classe Serializer (objet): @staticmethod def serialize (obj): def check (o): pour k, v dans o .__ dict __. Items (): try: _ = json .dumps (v) o .__ dict __ [k] = v sauf 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)

From doc : Le paramètre default(obj)est une fonction qui devrait renvoyer une version sérialisable d'obj ou augmenter TypeError. La valeur par défaut defaultdéclenche simplement TypeError.
luckydonald

4

jaraco a donné une réponse assez soignée. J'avais besoin de réparer quelques petites choses, mais cela fonctionne:

Code

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

Notez que nous avons besoin de deux étapes pour le chargement. Pour l'instant, la __python__propriété n'est pas utilisée.

Est-ce courant?

En utilisant la méthode d' AlJohri , je vérifie la popularité des approches:

Sérialisation (Python -> JSON):

Désérialisation (JSON -> Python):


4

Cela a bien fonctionné pour moi:

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__

et alors

class FileItem(JsonSerializable):
    ...

et

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

3

Si cela ne vous dérange pas d'installer un paquet pour lui, vous pouvez utiliser json-tricks :

pip install json-tricks

Après cela, il vous suffit d'importer dump(s)depuis json_tricksau lieu de json, et cela fonctionnera généralement:

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

ce qui donnera

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

Et c'est tout!


Cela fonctionnera très bien en général. Il y a quelques exceptions, par exemple si des choses spéciales se produisent __new__ou s'il y a plus de magie de métaclasse.

Évidemment, le chargement fonctionne également (sinon à quoi ça sert):

from json_tricks import loads
json_str = loads(json_str)

Cela suppose qu'il module_name.test_class.MyTestClspeut être importé et n'a pas changé de manière non compatible. Vous récupérerez une instance , pas un dictionnaire ou quelque chose, et ce devrait être une copie identique à celle que vous avez sauvegardée.

Si vous souhaitez personnaliser la façon dont quelque chose est (dé) sérialisé, vous pouvez ajouter des méthodes spéciales à votre classe, comme ceci:

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

qui sérialise seulement une partie des paramètres d'attributs, par exemple.

Et en bonus gratuit, vous obtenez la (dé) sérialisation des tableaux numpy, la date et les heures, les cartes commandées, ainsi que la possibilité d'inclure des commentaires dans json.

Avertissement: J'ai créé json_tricks , car j'ai eu le même problème que vous.


1
Je viens de tester json_tricks et cela a fonctionné à merveille (en 2019).
pauljohn32

2

jsonweb semble être la meilleure solution pour moi. Voir 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"}'

Cela fonctionne-t-il bien pour les objets imbriqués? Y compris le décodage et l'encodage
Simone Zandara

1

Voici mes 3 cents ...
Cela démontre la sérialisation json explicite pour un objet python arborescent.
Remarque: Si vous vouliez réellement du code comme celui-ci, vous pouvez utiliser la classe FilePath torsadée .

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)

1

J'ai rencontré ce problème lorsque j'ai essayé de stocker le modèle de Peewee dans PostgreSQL JSONField.

Après avoir lutté pendant un certain temps, voici la solution générale.

La clé de ma solution passe par le code source de Python et se rend compte que la documentation du code (décrite ici ) explique déjà comment étendre l'existant json.dumpspour prendre en charge d'autres types de données.

Supposons que vous ayez actuellement un modèle qui contient certains champs qui ne sont pas sérialisables en JSON et que le modèle qui contient le champ JSON ressemble à l'origine à ceci:

class SomeClass(Model):
    json_field = JSONField()

Définissez simplement une coutume JSONEncodercomme celle-ci:

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)

Et puis il suffit de l'utiliser dans votre JSONFieldcomme ci-dessous:

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

La clé est la default(self, obj)méthode ci-dessus. Pour chaque ... is not JSON serializableplainte que vous recevez de Python, ajoutez simplement du code pour gérer le type unserializable-to-JSON (tel que Enumoudatetime )

Par exemple, voici comment je prends en charge une classe héritant 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)

Enfin, avec le code implémenté comme ci-dessus, vous pouvez simplement convertir n'importe quel modèle Peewee en un objet JSON-seriazable comme ci-dessous:

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

Bien que le code ci-dessus soit (quelque peu) spécifique à Peewee, mais je pense:

  1. Il est applicable à d'autres ORM (Django, etc.) en général
  2. De plus, si vous avez compris comment cela json.dumpsfonctionne, cette solution fonctionne également avec Python (sans ORM) en général aussi

Pour toute question, veuillez poster dans la section commentaires. Merci!


1

Cette fonction utilise la récursivité pour itérer sur chaque partie du dictionnaire, puis appelle les méthodes repr () des classes qui ne sont pas des types intégrés.

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__()


0

J'ai trouvé ma propre solution. Utilisez cette méthode, passez n'importe quel document ( dict , list , ObjectId etc.) à sérialiser.

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

0

J'ai choisi d'utiliser des décorateurs pour résoudre le problème de sérialisation des objets datetime. Voici mon code:

#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

En important le module ci-dessus, mes autres modules utilisent json de manière normale (sans spécifier le mot-clé par défaut) pour sérialiser des données contenant des objets date-heure. Le code de sérialisation datetime est automatiquement appelé pour json.dumps et json.dump.


0

J'ai préféré la méthode de Lost Koder. J'ai rencontré des problèmes lors de la tentative de sérialisation d'objets plus complexes dont les membres / méthodes ne sont pas sérialisables. Voici mon implémentation qui fonctionne sur plus d'objets:

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)

0

Si vous êtes en mesure d'installer un package, je vous recommande d'essayer l' aneth , qui a très bien fonctionné pour mon projet. Une bonne chose à propos de ce package est qu'il a la même interface que pickle, donc si vous avez déjà utilisé pickledans votre projet, vous pouvez simplement le remplacer dillet voir si le script s'exécute, sans changer de code. C'est donc une solution très bon marché à essayer!

(Anti-divulgation complète: je ne suis en aucun cas affilié et n'ai jamais contribué au projet Dill.)

Installez le package:

pip install dill

Modifiez ensuite votre code à importer dillau lieu de pickle:

# import pickle
import dill as pickle

Exécutez votre script et voyez si cela fonctionne. (Si c'est le cas, vous souhaiterez peut-être nettoyer votre code afin de ne plus masquer le picklenom du module!)

Quelques détails sur les types de données qui dillpeuvent et ne peuvent pas être sérialisés, à partir de la page du projet :

dill peut décaper les types standard suivants:

none, type, bool, int, long, float, complex, str, unicode, tuple, list, dict, file, buffer, builtin, old and new style classes, instances of old and new style classes, set, frozenset, array , fonctions, exceptions

dill peut également décaper des types standard plus «exotiques»:

fonctions avec rendements, fonctions imbriquées, lambdas, cellule, méthode, méthode non liée, module, code, wrapper de méthode, dictproxy, descripteur de méthode, getsetdescriptor, memberdescriptor, wrapperdescriptor, xrange, slice, notimplemented, ellipsis, quit

dill ne peut pas encore décaper ces types standard:

frame, générateur, traceback



0

Pour ajouter une autre option: Vous pouvez utiliser le attrspackage et la asdictméthode.

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

json.dumps(objects, cls=ObjectEncoder)

et pour reconvertir

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 classe ressemble à ceci

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

0

En plus de la réponse d'Onur , vous voudrez peut-être traiter le type datetime comme ci-dessous.
(afin de gérer: l'objet 'datetime.datetime' n'a pas d'attribut ' dict ' d'exception.)

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

Usage:

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

0

Nous devons d'abord rendre notre objet conforme à JSON, afin que nous puissions le vider à l'aide du module JSON standard. Je l'ai fait de cette façon:

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

0

S'appuyant sur la réponse 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)

Les différences sont

  1. Fonctionne pour tout itérable au lieu de juste listet tuple(cela fonctionne pour les tableaux NumPy, etc.)
  2. Fonctionne pour les types dynamiques (ceux qui contiennent un __dict__).
  3. Inclut les types natifs floatet Nonedonc ils ne sont pas convertis en chaîne.

Laissé comme un exercice au lecteur est de gérer __slots__, les classes qui sont à la fois itérables et ont des membres, les classes qui sont des dictionnaires et ont également des membres, etc.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.