J'essaie de créer une représentation sous forme de chaîne JSON d'une instance de classe et j'ai des difficultés. Disons que la classe est construite comme ceci:
class testclass:
value1 = "a"
value2 = "b"
Un appel à json.dumps est effectué 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 également essayé d'utiliser le module pickle:
t = testclass()
print(pickle.dumps(t, pickle.HIGHEST_PROTOCOL))
Et il 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?
python
json
serialization
pickle
ferhan
la source
la source
s = json.dumps(obj, default=lambda x: x.__dict__)
pour les variables d'instance d'objet serialize (self.value1
,self.value2
, ...). C'est la manière la plus simple et la plus directe. Il sérialisera les structures d'objets imbriqués. Ladefault
fonction est appelée lorsqu'un objet donné n'est pas directement sérialisable. Vous pouvez également consulter ma réponse ci-dessous. J'ai trouvé les réponses populaires inutilement complexes, ce qui était probablement vrai il y a assez longtemps.testclass
n'a pas de__init__()
méthode, donc toutes les instances partageront les deux mêmes attributs de classe (value1
etvalue2
) définis dans l'instruction de classe. Comprenez-vous la différence entre une classe et une instance d'une?Réponses:
Le problème de base est que l'encodeur JSON
json.dumps()
ne sait comment sérialiser qu'un ensemble limité de types d'objets par défaut, tous les types intégrés. Liste ici: https://docs.python.org/3.3/library/json.html#encoders-and-decodersUne bonne solution serait de faire en sorte que votre classe hérite de
JSONEncoder
laJSONEncoder.default()
fonction, puis l'implémente , et que cette fonction émette le JSON correct pour votre classe.Une solution simple serait de faire appel
json.dumps()
au.__dict__
membre de cette instance. C'est un Python standarddict
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 cet article de blog:
Sérialisation d'objets Python arbitraires en JSON à l'aide de __dict__
la source
.__init__()
fonction de méthode, donc votre instance de classe a un dictionnaire vide. En d'autres termes,{}
est le résultat correct pour votre exemple de code.is not JSON serializable
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, ce 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 ifs sont pour la sérialisation de la date et de l'heure, puis il y a un
obj.__dict__
retour pour tout autre objet.l'appel final ressemble à:
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'à présent, cela a si bien fonctionné pour moi, dans l'attente de vos pensées.
la source
NameError: name 'serialize' is not defined
. Des conseils?try: dict = obj.__dict__ except AttributeError: dict = {s: getattr(obj, s) for s in obj.__slots__ if hasattr(obj, s)} return dict
Vous pouvez spécifier le
default
paramètre nommé dans lajson.dumps()
fonction:json.dumps(obj, default=lambda x: x.__dict__)
Explication:
Formez les documents ( 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.
(Fonctionne sur Python 2.7 et Python 3.x)
Remarque: Dans ce cas, vous avez besoin de
instance
variables et non declass
variables, comme l'exemple de la question tente de le faire. (Je suppose que le demandeur est censéclass instance
être un objet d'une classe)J'ai appris cela en premier grâce à 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.
la source
default=lambda x: getattr(x, '__dict__', str(x))
datetime.date
est une implémentation C donc il n'a pas d'__dict__
attribut.datetime.date
ÀJe fais juste:
Ce n'est pas la réponse complète, et si vous avez une sorte de classe d'objets compliquée, vous n'obtiendrez certainement pas tout. Cependant, je l'utilise pour certains de mes objets simples.
Une sur laquelle cela fonctionne très bien est la classe "options" que vous obtenez du module OptionParser. Le 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)
la source
Utilisation de jsonpickle
import jsonpickle object = YourClass() json_object = jsonpickle.encode(object)
la source
JSON n'est pas vraiment destiné à sérialiser des objets Python arbitraires. C'est génial pour sérialiser des
dict
objets, mais lepickle
module est vraiment ce que vous devriez utiliser en général. La sortie depickle
n'est pas vraiment lisible par l'homme, mais elle devrait être décochée très bien. Si vous insistez pour utiliser JSON, vous pouvez consulter lejsonpickle
module, qui est une approche hybride intéressante.https://github.com/jsonpickle/jsonpickle
la source
Python3.x
La meilleure approche que j'ai pu atteindre avec mes connaissances était celle-ci.
Notez que ce code traite également set ().
Cette approche est générique et nécessite simplement l'extension de classe (dans le deuxième exemple).
Notez que je ne fais que des 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 de différentes manières. Je suppose qu'un constructeur par défaut 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 un peu plus de recherche, j'ai trouvé un moyen de généraliser sans avoir besoin de l' appel de la méthode de registre SUPERCLASS , en utilisant une métaclasse
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
la source
Voici deux fonctions simples pour la sérialisation de toutes les classes non sophistiquées, rien d'extraordinaire comme expliqué précédemment.
J'utilise ceci pour des trucs de type de configuration car je peux ajouter de nouveaux membres aux classes sans ajustement de 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}
la source
Il y a quelques bonnes réponses sur la façon de commencer à faire cela. Mais il y a certaines choses à garder à l'esprit:
__slots__
place de__dict__
?json-tricks est une bibliothèque (que j'ai créée et à laquelle d'autres ont contribué) 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
pour ne pas oublier certains cas particuliers (comme__slots__
).Il fait également d'autres types comme les tableaux numpy, datetimes, nombres complexes; il permet également les commentaires.
la source
Je crois qu'au lieu de l'héritage comme suggéré dans la réponse acceptée, il vaut mieux utiliser le polymorphisme. Sinon, vous devez avoir une grosse instruction if else pour personnaliser l'encodage de chaque objet. Cela signifie créer un encodeur par défaut générique pour JSON en tant que:
def jsonDefEncoder(obj): if hasattr(obj, 'jsonEnc'): return obj.jsonEnc() else: #some default behavior return obj.__dict__
puis avoir une
jsonEnc()
fonction dans chaque classe que vous souhaitez sérialiser. par exempleclass A(object): def __init__(self,lengthInFeet): self.lengthInFeet=lengthInFeet def jsonEnc(self): return {'lengthInMeters': lengthInFeet * 0.3 } # each foot is 0.3 meter
Alors tu appelles
json.dumps(classInstance,default=jsonDefEncoder)
la source
Vous pouvez utiliser jsonic pour sérialiser à peu près n'importe quoi en JSON:
https://github.com/OrrBin/Jsonic
Exemple:
class TestClass: def __init__(self): self.x = 1 self.y = 2 instance = TestClass() s = serialize(instance): # instance s set to: {"x":1, "y":2} d = deserialize(s) # d is a new class instance of TestClass
pythonic a quelques fonctionnalités intéressantes comme la déclaration d'attributs de classe transitoire et la désérialisation sûre de type.
(Quelques années de retard avec la réponse, mais je pense que cela pourrait aider les autres)
la source