J'essaie de créer une représentation sous forme de chaîne JSON d'une instance de classe et rencontre des difficultés. Disons que la classe est construite comme ceci:
class testclass:
value1 = "a"
value2 = "b"
Un appel à json.dumps est fait comme ceci:
t = testclass()
json.dumps(t)
Il échoue et me dit que la classe de test n'est pas sérialisable JSON.
TypeError: <__main__.testclass object at 0x000000000227A400> is not JSON serializable
J'ai aussi essayé d'utiliser le module cornichon:
t = testclass()
print(pickle.dumps(t, pickle.HIGHEST_PROTOCOL))
Et cela donne des informations sur l'instance de classe, mais pas un contenu sérialisé de l'instance de classe.
b'\x80\x03c__main__\ntestclass\nq\x00)\x81q\x01}q\x02b.'
Qu'est-ce que je fais mal?
Le problème de base est que l'encodeur JSON json.dumps()
ne sait comment sérialiser qu'un ensemble limité de types d'objet par défaut, tous les types intégrés. Liste ici: https://docs.python.org/3.3/library/json.html#encoders-and-decoders
Une bonne solution serait de faire en sorte que votre classe hérite de JSONEncoder
, puis implémente la fonction JSONEncoder.default()
et que cette fonction émette le code JSON correct pour votre classe.
Une solution simple consisterait à appeler json.dumps()
sur le membre .__dict__
de cette instance. C'est un standard Python dict
et si votre classe est simple, elle sera sérialisable 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}
L'approche ci-dessus est discutée dans ce billet de blog:
Sérialisation d'objets Python arbitraires sur JSON à l'aide de __dict __
Il y a une façon qui fonctionne très bien pour moi que vous pouvez essayer:
json.dumps()
peut prendre un paramètre facultatif par défaut où vous pouvez spécifier une fonction de sérialiseur personnalisée pour les types inconnus, qui dans mon cas ressemble à
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__
Les deux premiers if concernent la sérialisation de la date et de l'heure, puis un obj.__dict__
est renvoyé pour tout autre objet.
l'appel final ressemble à:
json.dumps(myObj, default=serialize)
C'est particulièrement utile lorsque vous sérialisez une collection et que vous ne voulez pas appeler __dict__
explicitement pour chaque objet. Ici c'est fait pour vous automatiquement.
Jusqu'ici a travaillé si bien pour moi, dans l'attente de vos pensées.
Vous pouvez spécifier le paramètre nommé default
dans la fonction json.dumps()
:
json.dumps(obj, default=lambda x: x.__dict__)
Explication:
``default(obj)`` is a function that should return a serializable version
of obj or raise TypeError. The default simply raises TypeError.
(Fonctionne sur Python 2.7 et Python 3.x)
Remarque: dans ce cas, vous avez besoin de variables instance
et non de variables class
, comme le montre l'exemple de la question. (Je suppose que le demandeur voulait dire class instance
être un objet d'une classe)
J'ai d'abord appris cela de la réponse de @ phihag ici . J'ai trouvé que c'était le moyen le plus simple et le plus propre de faire le travail.
Je fais juste:
data=json.dumps(myobject.__dict__)
Ce n'est pas la réponse complète, et si vous avez une sorte de classe d'objet compliquée, vous n'obtiendrez certainement pas tout. Cependant, je l'utilise pour certains de mes objets simples.
La classe "options" du module OptionParser est particulièrement efficace. La voici avec la requête JSON elle-même.
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)
Utilisation de jsonpickle
import jsonpickle
object = YourClass()
json_object = jsonpickle.encode(object)
Voici deux fonctions simples pour la sérialisation de toute classe non sophistiquée, rien d’extraordinaire comme expliqué précédemment.
J'utilise ceci pour le type de configuration car je peux ajouter de nouveaux membres aux classes sans ajustement du code.
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}
JSON n'est pas vraiment destiné à la sérialisation d'objets Python arbitraires. C'est bien pour sérialiser les objets dict
, mais le module pickle
est vraiment ce que vous devriez utiliser en général. La sortie de pickle
n'est pas vraiment lisible par l'homme, mais elle devrait se décoiffer correctement. Si vous insistez pour utiliser JSON, vous pouvez consulter le module jsonpickle
, qui constitue une approche hybride intéressante.
Je crois qu'au lieu de l'héritage comme suggéré dans la réponse acceptée, il est préférable d'utiliser le polymorphisme. Sinon, vous devez avoir une instruction big if else pour personnaliser le codage de chaque objet. Cela signifie créer un encodeur générique par défaut pour JSON en tant que:
def jsonDefEncoder(obj):
if hasattr(obj, 'jsonEnc'):
return obj.jsonEnc()
else: #some default behavior
return obj.__dict__
et ensuite avoir une fonction jsonEnc()
dans chaque classe que vous souhaitez sérialiser. par exemple.
class A(object):
def __init__(self,lengthInFeet):
self.lengthInFeet=lengthInFeet
def jsonEnc(self):
return {'lengthInMeters': lengthInFeet * 0.3 } # each foot is 0.3 meter
Ensuite, vous appelez json.dumps(classInstance,default=jsonDefEncoder)
Python3.x
La meilleure approche que je pouvais atteindre avec ma connaissance était la suivante.
Notez que ce code traite également set ().
Cette approche est générique et ne nécessite que l’extension de classe (dans le deuxième exemple).
Notez que je ne le fais que pour les fichiers, mais il est facile de modifier le comportement à votre goût.
Cependant, il s'agit d'un CoDec.
Avec un peu plus de travail, vous pouvez construire votre classe d’une autre manière. Je suppose un constructeur par défaut pour l'instance, puis je mets à jour la classe dict.
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)
Éditer
Avec quelques recherches supplémentaires, j'ai trouvé un moyen de généraliser sans recourir à l'appel de la méthode SUPERCLASS, en utilisant un métaclasse
import json
import collections
REGISTERED_CLASS = {}
class MetaSerializable(type):
def __call__(cls, *args, **kwargs):
if cls.__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
Il existe de bonnes réponses sur la façon de s'y prendre. Mais il y a certaines choses à garder à l'esprit:
__slots__
au lieu de __dict__
?json-tricks est une bibliothèque (que j'ai créée et à laquelle j'ai contribué) et qui est capable de le faire depuis un certain temps. Par exemple:
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)
Vous récupérerez votre instance. Ici le json ressemble à ceci:
{
"__instance_type__": [
"json_tricks.test_class",
"MyTestCls"
],
"attributes": {
"s": "ub",
"dct": {
"7": 7
}
}
}
Si vous aimez créer votre propre solution, vous pouvez regarder la source de json-tricks
afin de ne pas oublier certains cas particuliers (comme __slots__
).
Il fait aussi d’autres types comme les tableaux numpy, les datetime, les nombres complexes; cela permet aussi des commentaires.