Cum sa faci un Piton clasa serializable?
Un simplu curs:
class FileItem:
def __init__(self, fname):
self.fname = fname
Ce ar trebui să fac pentru a fi capabil de a obține de ieșire de:
>>> import json
>>> my_file = FileItem('/foo/bar')
>>> json.dumps(my_file)
TypeError: Object of type 'FileItem' is not JSON serializable
Fara eroare
Aici este o soluție simplă pentru o caracteristică simplu:
.toJSON()
MetodaÎn loc de un JSON serializable clasă, să pună în aplicare un serializer metoda:
import json
class Object:
def toJSON(self):
return json.dumps(self, default=lambda o: o.__dict__,
sort_keys=True, indent=4)
Deci, tu doar suna-l pentru a serializa:
me = Object()
me.name = "Onur"
me.age = 35
me.dog = Object()
me.dog.name = "Apollo"
print(me.toJSON())
va ieșire:
{
"age": 35,
"dog": {
"name": "Apollo"
},
"name": "Onur"
}
Ai o idee despre rezultatul așteptat? De exemplu, va face acest lucru?
>>> f = FileItem("/foo/bar")
>>> magic(f)
'{"fname": "/foo/bar"}'
În acest caz, puteți pur și simplu apel json.haldele(f.__dict__)
.
Dacă doriți mai multe personalizate de ieșire, atunci va trebui să subclasa JSONEncoder
și să pună în aplicare propriul dvs. personalizat de serializare.
Pentru un exemplu banal, vezi mai jos.
>>> from json import JSONEncoder
>>> class MyEncoder(JSONEncoder):
def default(self, o):
return o.__dict__
>>> MyEncoder().encode(f)
'{"fname": "/foo/bar"}'
Apoi te trece clasa în json.haldele()
metoda ca cls
kwarg:
json.dumps(cls=MyEncoder)
Dacă doriți, de asemenea, pentru a decoda atunci'll trebuie să furnizeze un obicei object_hook
la JSONDecoder
clasă. De exemplu
>>> 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>
>>>
Pentru mai complex clase ai putea lua în considerare instrument de jsonpickle:
jsonpickle este o bibliotecă Python pentru serializarea și deserializarea de complex Python obiecte la și de la JSON.
standard De biblioteci Python pentru codificarea în Python JSON, cum ar fi stdlib e json, simplejson, și demjson, pot ocupa doar Python primitivi care au un impact direct JSON echivalent (de exemplu, previziuni, liste, siruri de caractere, int, etc.). jsonpickle se bazează pe partea de sus a acestor biblioteci și permite mai multe structuri de date complexe pentru a fi serializat pentru a JSON. jsonpickle este extrem de configurabil și extensibil, care permite utilizatorului de a alege JSON backend și se adaugă suplimentar backend.
Cele mai multe dintre răspunsurile implica schimbarea apel la json.haldele(), care nu este întotdeauna posibil sau de dorit (se poate întâmpla în interiorul unui cadru componentă de exemplu).
Dacă doriți să fie capabil de a apela json.haldele(obj) cum este, apoi, o soluție simplă este moștenirea de la 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
Aceasta funcționează dacă clasa ta este doar de bază de reprezentare a datelor, pentru mai complicată lucruri pe care le puteți seta întotdeauna cheile în mod explicit.
O altă opțiune este să-și încheie JSON dumping în propria sa clasă:
import json
class FileItem:
def __init__(self, fname):
self.fname = fname
def __repr__(self):
return json.dumps(self.__dict__)
Sau, chiar mai bine, subclasarea FileItem-class de la un JsonSerializable
clasa:
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
Testare:
>>> f = FileItem('/foo/bar')
>>> f.toJson()
'{"fname": "/foo/bar"}'
>>> f
'{"fname": "/foo/bar"}'
>>> str(f) # string coercion
'{"fname": "/foo/bar"}'
Îmi place Onur's a răspunde dar se va extinde pentru a include un opțional `toJSON () metoda pentru obiecte pentru a serializa ei înșiși:
def dumper(obj):
try:
return obj.toJSON()
except:
return obj.__dict__
print json.dumps(some_big_object, default=dumper, indent=2)
Trebuie doar să adăugați to_json
metoda la clasa ta astfel:
def to_json(self):
return self.message # or how you want it to be serialized
Și adăugați acest cod (din acest răspuns), undeva în partea de sus de tot:
from json import JSONEncoder
def _default(self, obj):
return getattr(obj.__class__, "to_json", _default.default)(obj)
_default.default = JSONEncoder().default
JSONEncoder.default = _default
Acest lucru va maimuță-patch json modulul când l's importate, astfel JSONEncoder.default() verifică automat pentru o special "to_json()" metoda și folosește-l pentru a codifica în cazul în care obiectul a fost găsit.
Doar ca Onur a spus, dar de data asta nu't trebuie să actualizeze în fiecare json.haldele()
în proiectul dumneavoastră.
Am dat peste această problemă și implementat o versiune mai generală a unui Codor pentru Python obiecte care pot manevrați obiecte imbricate și moștenit domenii:
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
Exemplu:
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)
Rezultatul:
{
"a": "A",
"b": [
{
"ab": {
"b": "B",
"i": "I",
"y": "y"
}
}
],
"c": {
"c": "YES"
},
"i": "I"
}
Daca're folosind Python3.5+, ai putea folosi jsons
. Acesta va converti obiect (și toate atributele sale recursiv) de la un dict.
import jsons
a_dict = jsons.dump(your_object)
Sau dacă ai vrut un șir de caractere:
a_str = jsons.dumps(your_object)
Sau dacă clasa ta implementat jsons.JsonSerializable
:
a_dict = your_object.json
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', '[email protected]')))
dacă utilizați standard json
, u nevoie pentru a defini un "default" funcția
import json
def default(o):
return o._asdict()
print(json.dumps(User('alice', '[email protected]'), default=default))
json
este limitat în termeni de obiecte se poate imprima, și jsonpickle
(aveți nevoie de un pip instala jsonpickle
) este limitată în ceea ce se poate't liniuță textul. Dacă doriți să verificați conținutul unui obiect a cărui clasă nu se poate't schimba, eu încă n't găsi un mai drept decât:
import json
import jsonpickle
...
print json.dumps(json.loads(jsonpickle.encode(object)), indent=2)
Notă: faptul că încă se poate't de imprimare obiect metodele.
Această clasă poate face truc, se convertește obiect standard json .
import json
class Serializer(object):
@staticmethod
def serialize(object):
return json.dumps(object, default=lambda o: o.__dict__.values()[0])
utilizare:
Serializer.serialize(my_object)
de lucru în python2.7 " și " python3
.
Aceasta a lucrat bine pentru mine:
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__
și apoi
class FileItem(JsonSerializable):
...
și
log.debug(json.dumps(<my object>, default=JsonSerializable.dumper, indent=2))
jaraco a dat-o destul de elegant răspunsul. Am nevoie pentru a repara unele lucruri minore, dar acest lucru funcționează:
# 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__
Rețineți că avem nevoie de două etape pentru încărcare. Pentru moment, `python proprietate nu este folosit.
Folosind metoda de AlJohri, am verifica popularitatea de abordări:
Serialization (Python -> JSON):
to_json
: 266,595 pe 2018-06-27toJSON
: 96,307 pe 2018-06-27__json__
: 8,504 pe 2018-06-27for_json
: 6,937 pe 2018-06-27Deserializarea (JSON -> Python):
from_json
: 226,101 pe 2018-06-27import 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)
Dacă tu nu't mintea instalarea unui pachet pentru aceasta, puteți utiliza json-trucuri:
pip install json-tricks
După care aveți nevoie doar de a importa basculante(s) "de la" json_tricks
în loc de json, și-l'll, de obicei, locul de muncă:
from json_tricks import dumps
json_str = dumps(cls_instance, indent=4)
care'll da
{
"__instance_type__": [
"module_name.test_class",
"MyTestCls"
],
"attributes": {
"attr": "val",
"dct_attr": {
"hello": 42
}
}
}
Și că's practic!
Acest lucru va lucra mare în general. Există unele excepții, de exemplu, dacă speciale lucrurile care se întâmpla în __noi,__
, sau mai metaclass magia se întâmplă.
Evident, de încărcare, de asemenea, funcționează (în caz contrar, ceea ce's punct):
from json_tricks import loads
json_str = loads(json_str)
Acest lucru presupune că module_name.test_class.MyTestCls
pot fi importate și n't s-a schimbat în non-compatibile moduri. Tu'll a obține înapoi o instanță, nu un dicționar sau ceva, și ar trebui să fie o copie identică cu cea pe care ai părăsit.
Dacă doriți să personalizați modul în care ceva se (de)serializat, puteți adăuga metode speciale la clasă, astfel:
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
care serializes doar o parte din atributele parametri, ca un exemplu.
Și ca un bonus gratuit, veți obține (de)serializare de numpy tablouri, data & ori, ordonat hărți, precum și capacitatea de a include comentarii în json.
Disclaimer: am creat json_tricks, pentru că am avut aceeasi problema ca tine.
jsonweb pare a fi cea mai bună soluție pentru mine. Vezi 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"}'
Aici este meu de 3 centi ...
Acest lucru demonstrează explicit json serialization pentru un copac-ca obiect python.
Notă: Dacă ați vrut de fapt un cod de genul asta ai putea folosi twisted
FilePath clasă.
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)
M-am întâlnit cu această problemă atunci când am încercat pentru a stoca Pitic's model în PostgreSQL JSONField
.
După ce se luptă pentru un timp, aici's soluția generală.
Cheia mea soluție este de a merge prin Python's de cod sursă și dându-și seama că codul de documentare (descris aici) deja explică cum de a prelungi durata existente `json.haldele de a sprijini alte tipuri de date.
Să presupunem că curente au un model care conține unele domenii care nu sunt serializabile la JSON și modelul care conține câmpul JSON inițial pare ca acest lucru:
class SomeClass(Model):
json_field = JSONField()
Doar defini un obicei JSONEncoder
astfel:
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)
Și apoi doar să-l folosească în JSONField
ca mai jos:
class SomeClass(Model):
json_field = JSONField(dumps=CustomJsonEncoder.json_dumper)
Cheia este default(auto, obj)metoda de mai sus. Pentru fiecare
... nu este JSON serializableplângere primiți de la Python, trebuie doar să adăugați cod să se ocupe de unserializable-să-tip JSON (cum ar fi
Enumsau
datetime`)
De exemplu, aici's cum susțin o clasă moștenește de la 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)
În cele din urmă, cu codul implementat ca mai sus, puteți converti orice Pitic modele pentru a fi un JSON-seriazable obiect ca mai jos:
peewee_model = WhateverPeeweeModel()
new_model = SomeClass()
new_model.json_field = model_to_dict(peewee_model)
Deși codul de mai sus a fost (oarecum) specifice Pitic, dar cred că:
Orice întrebări, vă rugăm să posta în secțiunea de comentarii. Multumesc!!!
Mi-a placut Pierdut Koder's metoda cel mai mult. Am fugit în probleme atunci când încearcă pentru a serializa mai complexe obiecte a caror membri/metode sunt't serializable. Aici's punerea în aplicare a mea, care funcționează pe mai multe obiecte:
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)