Cum se face serializabil o clasă JSON

Cum se face serializabil o clasă Python?

O clasă simplă:

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

Ce ar trebui să fac pentru a putea obține rezultate de la:

>>> import json

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

Fără eroare


person Sergey    schedule 22.09.2010    source sursă
comment
Este regretabil că toate răspunsurile par să răspundă la întrebarea Cum serializez o clasă? mai degrabă decât întrebarea de acțiune Cum fac o clasă serializabilă? Aceste răspunsuri presupun că realizați singur serializarea, mai degrabă decât să transmiteți obiectul unui alt modul care îl serializează.   -  person Kyle Delaney    schedule 18.10.2019
comment
Dacă utilizați Python3.5+, puteți folosi jsons. Acesta vă va converti obiectul (și toate atributele sale în mod recursiv) într-un dict. import jsons vezi răspunsul de mai jos - funcționează perfect   -  person tswaehn    schedule 02.04.2020
comment
@KyleDelaney Speram într-adevăr ca o interfață/metodă magică pe care o pot implementa să devină și searializabilă. Cred că va trebui să implementez o funcție .to_dict() sau ceva care poate fi apelat pe obiect înainte de a fi transmis modulului care încearcă să-l serializeze.   -  person Felix B.    schedule 01.09.2020
comment
consultați stackoverflow.com/a/63718624/1497139 pentru un început pentru un mixin JSONAble   -  person Wolfgang Fahl    schedule 03.09.2020
comment
@FelixB. Puteți utiliza funcția încorporată vars în combinație cu json.dumps (vezi răspunsul meu stackoverflow.com/a/64469761/1587520 )   -  person user1587520    schedule 23.11.2020


Răspunsuri (33)


Aveți o idee despre rezultatul așteptat? De exemplu, asta va face?

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

În acest caz, puteți doar să sunați la json.dumps(f.__dict__).

Dacă doriți o ieșire mai personalizată, va trebui să subclasați JSONEncoder și să implementați propria dvs. serializare personalizată.

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 treceți această clasă în metoda json.dumps() ca cls kwarg:

json.dumps(cls=MyEncoder)

Dacă doriți și să decodați, va trebui să furnizați un object_hook personalizat 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>
>>> 
person Manoj Govindan    schedule 22.09.2010
comment
Utilizarea __dict__ nu va funcționa în toate cazurile. Dacă atributele nu au fost setate după ce obiectul a fost instanțiat, este posibil ca __dict__ să nu fie complet populat. În exemplul de mai sus, sunteți OK, dar dacă aveți atribute de clasă pe care doriți să le codificați, acestea nu vor fi listate în __dict__ decât dacă au fost modificate în apelul __init__ al clasei sau printr-un alt mod după ce obiectul a fost instanţiat. - person Kris Hardy; 29.12.2011
comment
+1, dar funcția from_json() folosită ca obiect-hook ar trebui să aibă o instrucțiune else: return json_object, astfel încât să se poată ocupa și de obiecte generale. - person jogojapan; 19.03.2013
comment
De asemenea, @KrisHardy __dict__ nu funcționează dacă utilizați __slots__ pe o nouă clasă de stil. - person badp; 13.12.2013
comment
Puteți utiliza un JSONEncoder personalizat ca mai sus pentru a crea un protocol personalizat, cum ar fi verificarea existenței metodei __json_serializable__ și apelarea acesteia pentru a obține o reprezentare serializabilă JSON a obiectului. Acest lucru ar fi în concordanță cu alte modele Python, cum ar fi __getitem__, __str__, __eq__ și __len__. - person jpmc26; 15.07.2015
comment
Sunt nou în python... putem folosi aceste biblioteci (json sau simplejson) pentru a serializa/deserializa obiecte care sunt utilizate pe scară largă de către dezvoltatorii python, spun Pandas dataframe, serie etc? - person Mahesha999; 20.09.2016
comment
__dict__, de asemenea, nu va funcționa recursiv, de exemplu, dacă un atribut al obiectului dvs. este un alt obiect. - person Neel; 10.04.2018
comment
JSONEncoder apelează numai metoda default a subclasei dacă datele date nu sunt standard. - person yoonghm; 18.09.2018
comment
@Mahesha999 utilizează metoda df.to_json() pentru Dataframe, Series etc - person user2561747; 07.12.2018

Iată o soluție simplă pentru o caracteristică simplă:

.toJSON() Metoda

În loc de o clasă serializabilă JSON, implementați o metodă de serializare:

import json

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

Așa că îl sunați doar pentru a serializa:

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

print(me.toJSON())

va scoate:

{
    "age": 35,
    "dog": {
        "name": "Apollo"
    },
    "name": "Onur"
}
person Onur Yıldırım    schedule 21.03.2013
comment
Foarte limitat. Dacă aveți un dict {foo:bar,baz:bat}, acesta se va serializa cu ușurință în JSON. Dacă, în schimb, aveți {foo:bar,baz:MyObject()}, atunci nu puteți. Situația ideală ar fi ca obiectele imbricate să fie serializate la JSON recursiv, nu explicit. - person Mark E. Haase; 22.08.2013
comment
Încă va funcționa. Îți lipsește o.__dict___. Încercați propriul exemplu: class MyObject(): def __init__(self): self.prop = 1 j = json.dumps({ "foo": "bar", "baz": MyObject() }, default=lambda o: o.__dict__) - person Onur Yıldırım; 23.08.2013
comment
Este aceasta solutie reversibila? i.e. Este ușor să reconstruiți obiectul din json? - person Jorge Leitao; 26.04.2015
comment
@J.C.Leitão Nu. Puteți avea două clase diferite cu aceleași câmpuri. Obiectele a și b din acea clasă (probabil cu aceleași proprietăți) ar avea același a.__dict__ / b.__dict__. - person Martin Thoma; 16.06.2015
comment
Cu toate acestea, probabil că ați putea adăuga un câmp unjson_class sau cam asa ceva, care oferă un indiciu ce clasă avea obiectul inițial. Apoi ați putea folosi introspecția modulului și să folosiți ceva de genul stackoverflow.com/a/2169191/562769 pentru a recupera obiectul . - person Martin Thoma; 16.06.2015
comment
Acest lucru nu funcționează cu datetime.datetime instanțe. Afișează următoarea eroare: 'datetime.datetime' object has no attribute '__dict__' - person Bruno Finger; 17.06.2015
comment
Următoarele vor funcționa pentru orice nu are un __dict__: def _try(o): try: return o.__dict__ except: return str(o) și def to_JSON(self): return json.dumps(self, default=lambda o: _try(o), sort_keys=True, indent=0, separators=(',',':')).replace('\n', '') - person Bruno Finger; 17.06.2015
comment
@J.C.Leitão da, este reversibil. când încărcați json, puteți adăuga fiecare pereche cheie/valoare la proprietatea obiectului dict, consultați stackoverflow.com/questions/6578986/ - person FistOfFury; 03.09.2017
comment
Probabil că îmi lipsește ceva, dar se pare că nu funcționează (adică, json.dumps(me) nu apelează la metoda toJSON a lui Object. - person cglacet; 24.08.2018
comment
Puteți folosi default=vars în loc de default=lambda o: o.__dict__. - person Matthew D. Scholefield; 24.02.2019
comment
@cglacet Asta pentru că nu a făcut clasa să fie serializabilă, doar a creat o metodă care scuipă șirul JSON. Acesta nu este un răspuns adecvat la întrebare, este mai mult un hack pentru cazuri speciale. Dar cel corect este mai sus. Dacă aveți nevoie ca YourObject să fie serializat ca parte/conținut al altui ParentObject, trebuie să creați un codificator. - person Javo; 08.09.2020
comment
Întrebarea se referă în mod specific la crearea json.dumps(). Nu cum să implementați singur serializarea JSON. - person Ernest; 23.10.2020

Pentru clase mai complexe, puteți lua în considerare instrumentul jsonpickle:

jsonpickle este o bibliotecă Python pentru serializarea și deserializarea obiectelor complexe Python către și de la JSON.

Bibliotecile standard Python pentru codificarea Python în JSON, cum ar fi json, simplejson și demjson ale stdlib, pot gestiona doar primitivele Python care au un echivalent JSON direct (de exemplu, dict, liste, șiruri de caractere, int etc.). jsonpickle se bazează pe aceste biblioteci și permite ca structurile de date mai complexe să fie serializate în JSON. jsonpickle este extrem de configurabil și extensibil, permițând utilizatorului să aleagă backend-ul JSON și să adauge backend-uri suplimentare.

(link către jsonpickle pe PyPi)

person gecco    schedule 23.12.2011
comment
Venind din C#, la asta mă așteptam. O linie simplă și fără să te încurci cu clasele. - person Jerther; 14.12.2015
comment
jsonpickle este minunat. A funcționat perfect pentru un obiect imens, complex, dezordonat, cu multe niveluri de clase - person wisbucky; 04.03.2016
comment
Există un exemplu de modalitate corectă de a salva acest lucru într-un fișier? Documentația arată doar cum să codificați și să decodați un obiect jsonpickle. De asemenea, acest lucru nu a putut decoda un dict de dict care conține cadre de date panda. - person user5359531; 16.08.2016
comment
@user5359531 puteți folosi obj = jsonpickle.decode(file.read()) și file.write(jsonpickle.encode(obj)). - person Kilian Batzner; 02.01.2017
comment
O întrebare specifică pentru django: utilizarea jsonpickle pentru serializarea datelor de sesiune are aceeași vulnerabilitate ca pickle? (așa cum este descris aici https://docs.djangoproject.com/en/1.11/topics/http/sessions/#bundled-serializers)? - person Paul Bormans; 23.06.2017
comment
Merge pentru mine!. Este ceea ce aveam nevoie. Am vrut doar să printez un obiect scenariu de comportament. - person matabares; 26.05.2020
comment
Aceasta este o soluție grozavă. În loc să construiesc o extensie complexă de decodor JSON pentru modulul json, am putut să-mi serializez clasa cu o singură linie: s=jsonpickle.encode(self). Restaurarea a fost la fel de simplă. Acesta este adevăratul spirit Python. +1 - person NoCake; 18.09.2020

Majoritatea răspunsurilor implică schimbarea apelului la json.dumps(), ceea ce nu este întotdeauna posibil sau de dorit (se poate întâmpla în interiorul unei componente cadru, de exemplu).

Dacă doriți să puteți apela json.dumps(obj) așa cum este, atunci 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

Acest lucru funcționează dacă clasa dvs. este doar reprezentarea de bază a datelor, pentru lucruri mai complicate puteți seta întotdeauna cheile în mod explicit.

person andyhasit    schedule 03.07.2015
comment
Aceasta poate fi într-adevăr o soluție bună :) Cred că pentru cazul meu este. Beneficii: comunicați forma obiectului făcându-l o clasă cu init, este în mod inerent serializabil și pare interpretabil ca repr. - person PascalVKooten; 22.09.2016
comment
Deși accesul cu puncte încă lipsește :( - person PascalVKooten; 22.09.2016
comment
Ahh pare să funcționeze! Mulțumesc, nu sunt sigur de ce acesta nu este răspunsul acceptat. Sunt total de acord că schimbarea dumps nu este o soluție bună. Apropo, în cele mai multe cazuri, probabil că doriți să aveți moștenirea dict împreună cu delegarea, ceea ce înseamnă că veți avea un atribut de tip dict în interiorul clasei dvs., apoi veți trece acest atribut ca parametru ca inițializare ceva de genul super().__init__(self.elements). - person cglacet; 24.08.2018
comment
În cazul meu de utilizare trebuia să stochez date care erau invizibile pentru json.dumps(), așa că am folosit această metodă. Clasa DictWithRider preia un obiect arbitrar, îl stochează ca membru și îl face accesibil printr-o funcție get_rider_obj() dar nu îl transmite la dict.__init__(). Deci, părți ale aplicației care doresc să vadă datele ascunse pot apela d.get_rider_obj(), dar json.dumps() vede practic un dict gol. După cum a menționat @PascalVKooten, nu puteți accesa membrii obișnuiți cu notație cu puncte, dar puteți accesa funcții. - person gkimsey; 21.07.2020
comment
pentru utilizare simplă, acesta este ideal. Notarea punctelor poate fi activată cu ușurință prin linii suplimentare ulterioare liniei dict.__init__( ca self.fname = fname și obiectul poate fi deserializat cu f = FileItem(**json.loads(serialised_f)) - person paddyg; 21.01.2021
comment
O iubesc absolut pe aceasta pentru simplitatea ei. Trebuie să vă proiectați complet clasele (fără corecție cu maimuță), dar funcționează foarte bine, chiar dacă membrii clasei sunt liste de obiecte. - person Cerno; 03.05.2021
comment
6 ani mai târziu, acesta este încă cel mai bun răspuns - person rossco; 09.07.2021
comment
Este exact ceea ce am nevoie, mulțumesc mult - person Qingyi Wu; 19.07.2021

Îmi place Răspunsul lui Onur, dar m-ar extinde pentru a include o metodă opțională toJSON() pentru ca obiectele să se serializeze:

def dumper(obj):
    try:
        return obj.toJSON()
    except:
        return obj.__dict__
print json.dumps(some_big_object, default=dumper, indent=2)
person Jason S    schedule 27.01.2015
comment
Am considerat că acesta este cel mai bun echilibru între utilizarea json.dumps existentă și introducerea unei manipulări personalizate. Mulțumiri! - person Daniel Buckmaster; 15.04.2015
comment
Chiar îmi place asta; dar mai degrabă decât try-catch ar face probabil ceva de genul if 'toJSON' in obj.__attrs__():... pentru a evita o eroare silențioasă (în cazul unei eșecuri în toJSON() din alt motiv decât faptul că nu există)... un eșec care poate duce la coruperea datelor. - person thclark; 22.11.2017
comment
@thclark, după cum am înțeles, idomatic python cere iertare, nu permisiunea, așa că încercați-except este abordarea corectă, dar excepția corectă ar trebui să fie prinsă, un AttributeError în acest caz. - person Phil; 08.09.2020
comment
@phil cu câțiva ani mai în vârstă și mai înțelept acum, aș fi de acord cu tine. - person thclark; 08.09.2020
comment
Acest lucru chiar ar trebui să prindă un AttributeError în mod explicit - person juanpa.arrivillaga; 04.02.2021
comment
Și dacă AttributeError este ridicat în interiorul obj.toJSON()? - person artm; 16.05.2021

Doar adăugați metoda to_json la clasa dvs. astfel:

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

Și adăugați acest cod (de la acest răspuns), undeva în partea de sus a tuturor:

from json import JSONEncoder

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

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

Acesta va patch modulul json atunci când este importat, astfel încât JSONEncoder.default() verifică automat o metodă specială „to_json()” și o folosește pentru a codifica obiectul dacă este găsit.

Așa cum a spus Onur, dar de data aceasta nu trebuie să actualizați fiecare json.dumps() din proiectul dvs.

person Fancy John    schedule 04.08.2016
comment
Multe multumiri! Acesta este singurul răspuns care îmi permite să fac ceea ce vreau: să pot serializa un obiect fără a schimba codul existent. Celelalte metode de cele mai multe ori nu funcționează pentru mine. Obiectul este definit într-o bibliotecă terță parte, iar codul de serializare este de asemenea terță parte. Schimbarea lor va fi incomodă. Cu metoda ta, trebuie doar să fac TheObject.to_json = my_serializer. - person Yongwei Wu; 11.10.2017

O altă opțiune este să includeți dumpingul JSON î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 clasei FileItem dintr-o clasă JsonSerializable:

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"}'
person Paulo Freitas    schedule 16.06.2012
comment
Bună, nu prea îmi place această abordare personalizată a codificatorului, ar fi mai bine dacă puteți face clasa dvs. json seriazabilă. Încerc, și încerc și încerc și nimic. Există vreo idee cum să faci asta. Chestia este că modulul json vă testează clasa împotriva tipurilor Python încorporate și chiar spune că pentru clasele personalizate faceți codificatorul :). Poate fi falsificat? Așa că aș putea face ceva cu clasa mea, astfel încât să se comporte ca o listă simplă la modulul json? Încerc subclasscheck și instancecheck, dar nimic. - person Bojan Radojevic; 15.08.2012
comment
@ADRENALIN Ai putea moșteni de la un tip primar (probabil dict), dacă toate valorile atributelor de clasă sunt serializabile și nu te deranjează hack-urile. De asemenea, puteți utiliza jsonpickle sau json_tricks sau ceva în loc de cel standard (încă un codificator personalizat, dar nu unul pe care trebuie să îl scrieți sau să îl apelați). Primul decapatează instanța, cel de-al doilea o stochează ca dict de atribute, pe care le puteți modifica prin implementarea __json__encode__ / __json_decode__ (dezvăluire: eu l-am făcut pe ultimul). - person Mark; 20.10.2016
comment
Asta nu face obiectul serializabil pentru clasa json. Oferă doar o metodă pentru a obține un șir json returnat (trivial). Astfel json.dumps(f) va eșua. Nu asta s-a cerut. - person omni; 21.09.2020

După cum sa menționat în multe alte răspunsuri, puteți transmite o funcție către json.dumps pentru a converti obiectele care nu sunt unul dintre tipurile acceptate implicit într-un tip acceptat. În mod surprinzător, niciunul dintre ele nu menționează cel mai simplu caz, și anume utilizarea funcției încorporate vars pentru a converti obiectele într-un dict care să conțină toate atributele lor:

json.dumps(obj, default=vars)

Rețineți că aceasta acoperă numai cazuri de bază, dacă aveți nevoie de o serializare mai specifică pentru anumite tipuri (de exemplu, excluderea anumitor atribute sau pentru obiecte care nu au un atribut __dict__), trebuie să utilizați o funcție personalizată sau un JSONEncoder așa cum este descris în celelalte răspunsuri. .

person user1587520    schedule 21.10.2020
comment
nu este clar ce vrei să spui prin default=vars, înseamnă asta că vars este serializatorul implicit? Dacă nu: Acest lucru nu rezolvă cu adevărat cazul în care nu puteți influența modul în care este numit json.dumps. Dacă pur și simplu treceți un obiect unei biblioteci și acea bibliotecă apelează json.dumps pe acel obiect, nu prea ajută că ați implementat vars dacă acea bibliotecă nu folosește dumps în acest fel. În acest sens, este echivalent cu un JSONEncoder personalizat. - person Felix B.; 24.11.2020
comment
Ai dreptate, nu este altceva decât o simplă alegere pentru un serializator personalizat și nu rezolvă cazul pe care îl descrii. Dacă văd corect, nu există nicio soluție pentru cazul în care nu controlați modul în care json.dumps este invocat. - person user1587520; 25.11.2020
comment
Am rezolvat cazul meu. Pur și simplu genial! - person Rub; 02.12.2020
comment
Pentru unele obiecte, această abordare va arunca vars() argument must have __dict__ attribute - person JustAMartin; 24.02.2021

Am întâlnit această problemă zilele trecute și am implementat o versiune mai generală a unui codificator pentru obiecte Python, care poate tratează obiecte imbricate și câmpuri moștenite:

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)

Rezultat:

{
  "a": "A", 
  "b": [
    {
      "ab": {
        "b": "B", 
        "i": "I", 
        "y": "y"
      }
    }
  ], 
  "c": {
    "c": "YES"
  }, 
  "i": "I"
}
person tobigue    schedule 18.02.2016
comment
Deși acesta este puțin vechi..Mă confrunt cu o eroare de importuri circulare. Deci, în loc de return obj în ultima linie, am făcut acest return super(ObjectEncoder, self).default(obj). Consultați AICI - person SomeTypeFoo; 11.04.2017

Dacă utilizați Python3.5+, puteți utiliza jsons. (PyPi: https://pypi.org/project/jsons/) Acesta vă va converti obiectul (și toate atributele sale în mod recursiv) la un dict.

import jsons

a_dict = jsons.dump(your_object)

Sau dacă ai vrut un șir:

a_str = jsons.dumps(your_object)

Sau dacă clasa dvs. a implementat jsons.JsonSerializable:

a_dict = your_object.json
person R H    schedule 19.12.2018
comment
Dacă puteți utiliza Python 3.7+, am descoperit că cea mai curată soluție pentru a converti clasele Python în dict și șiruri JSON (și invers) este să amestecați biblioteca jsons cu dataclasses. Până acum, atât de bine pentru mine! - person Ruluk; 26.02.2019
comment
Aceasta este o bibliotecă externă, care nu este integrată în instalarea standard Python. - person Noumenon; 10.07.2019
comment
numai pentru clasa care are atributul slots - person yehudahs; 03.12.2019
comment
Puteți, dar nu trebuie să utilizați sloturi. Numai când dumpingul conform semnăturii unei anumite clase veți avea nevoie de sloturi. În viitoarea versiune 1.1.0, nu mai este cazul. - person R H; 04.12.2019

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 json standard, trebuie să definiți o funcție default

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

print(json.dumps(User('alice', '[email protected]'), default=default))
person tryer3000    schedule 17.06.2015
comment
Am simplificat acest lucru eliminând funcția _asdict cu o lambda json.dumps(User('alice', '[email protected]'), default=lambda x: x.__dict__) - person JustEngland; 29.11.2018

json este limitat în ceea ce privește obiectele pe care le poate imprima, iar jsonpickle (poate avea nevoie de un pip install jsonpickle) este limitat în termeni în care nu poate indenta text. Dacă doriți să inspectați conținutul unui obiect a cărui clasă nu o puteți schimba, tot nu am putut găsi o modalitate mai dreaptă decât:

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

Notă: că încă nu pot imprima metodele obiectului.

person ribamar    schedule 04.04.2016

Această clasă poate face truc, transformă obiectul în json standard.

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)

lucrează în python2.7 și python3.

person Lost Koder    schedule 09.10.2016
comment
Cel mai mult mi-a plăcut această metodă. Am întâmpinat probleme când am încercat să serializez obiecte mai complexe ale căror membri/metode nu sunt serializabile. Iată implementarea mea care funcționează pe mai multe obiecte: ``` class Serializer(obiect): @staticmethod def serialize(obj): def check(o): for k, v in o.__dict__.items(): try: _ = json .dumps(v) o.__dict__[k] = v cu excepția TypeError: o.__dict__[k] = str(v) return o return json.dumps(check(obj).__dict__, indent=2) ``` - person Will Charlton; 11.11.2017

Iată cei 3 cenți ai mei...
Acest lucru demonstrează serializarea json explicită pentru un obiect python asemănător arborelui.
Notă: dacă ați dorit de fapt un astfel de cod, puteți utiliza clasa FilePath răsucită.

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)
person Dan Brough    schedule 10.07.2013

jaraco a dat un răspuns destul de clar. Trebuia să repar niște lucruri minore, dar funcționează:

Cod

# 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 doi pași pentru încărcare. Deocamdată, proprietatea __python__ nu este utilizată.

Cât de comun este asta?

Folosind metoda AlJohri, verific popularitatea abordărilor:

Serializare (Python -> JSON):

Deserializare (JSON -> Python):

person Martin Thoma    schedule 27.06.2018

Acest lucru a funcționat 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))
person jmhostalet    schedule 18.01.2019

Dacă nu vă deranjează să instalați un pachet pentru acesta, puteți utiliza json-tricks:

pip install json-tricks

După aceea, trebuie doar să importați dump(s) din json_tricks în loc de json și, de obicei, va funcționa:

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

care va da

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

Și practic asta este!


Acest lucru va funcționa grozav în general. Există câteva excepții, de ex. dacă se întâmplă lucruri speciale în __new__, sau mai multă magie metaclasă se întâmplă.

Evident, funcționează și încărcarea (altfel ce rost are):

from json_tricks import loads
json_str = loads(json_str)

Aceasta presupune că module_name.test_class.MyTestCls poate fi importat și nu s-a schimbat în moduri necompatibile. Veți primi înapoi o instanță, nu un dicționar sau ceva de genul, și ar trebui să fie o copie identică cu cea pe care ați aruncat-o.

Dacă doriți să personalizați modul în care ceva este (de)serializat, puteți adăuga metode speciale la clasa dvs., cum ar fi:

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 serializează doar o parte din parametrii atributelor, de exemplu.

Și ca bonus gratuit, obțineți (de)serializarea matricelor numpy, date și ore, hărți ordonate, precum și posibilitatea de a include comentarii în json.

Disclaimer: am creat json_tricks, deoarece am avut aceeași problemă ca și dvs.

person Mark    schedule 10.11.2016
comment
Tocmai am testat json_tricks și a funcționat beautify (în 2019). - person pauljohn32; 06.11.2019

Comentariul lui Kyle Delaney este corect așa că am încercat să folosesc răspunsul https://stackoverflow.com/a/15538391/1497139, precum și o versiune îmbunătățită a

pentru a crea un mixin JSONAble.

Deci, pentru a face o clasă JSON serializabilă, utilizați JSONAble ca o super clasă și fie apelați:

 instance.toJSON()

or

 instance.asJSON()

pentru cele două metode oferite. De asemenea, puteți extinde clasa JSONAble cu alte abordări oferite aici.

Exemplul de testare pentru testul unitar cu eșantionul de familie și persoană are ca rezultat:

toJSOn():

{
    "members": {
        "Flintstone,Fred": {
            "firstName": "Fred",
            "lastName": "Flintstone"
        },
        "Flintstone,Wilma": {
            "firstName": "Wilma",
            "lastName": "Flintstone"
        }
    },
    "name": "The Flintstones"
}

asJSOn():

{'name': 'The Flintstones', 'members': {'Flintstone,Fred': {'firstName': 'Fred', 'lastName': 'Flintstone'}, 'Flintstone,Wilma': {'firstName': 'Wilma', 'lastName': 'Flintstone'}}}

Test unitar cu eșantion de familie și persoană

def testJsonAble(self):
        family=Family("The Flintstones")
        family.add(Person("Fred","Flintstone")) 
        family.add(Person("Wilma","Flintstone"))
        json1=family.toJSON()
        json2=family.asJSON()
        print(json1)
        print(json2)

class Family(JSONAble):
    def __init__(self,name):
        self.name=name
        self.members={}
    
    def add(self,person):
        self.members[person.lastName+","+person.firstName]=person

class Person(JSONAble):
    def __init__(self,firstName,lastName):
        self.firstName=firstName;
        self.lastName=lastName;

jsonable.py care definește mixul JSONAble

 '''
Created on 2020-09-03

@author: wf
'''
import json

class JSONAble(object):
    '''
    mixin to allow classes to be JSON serializable see
    https://stackoverflow.com/questions/3768895/how-to-make-a-class-json-serializable
    '''

    def __init__(self):
        '''
        Constructor
        '''
    
    def toJSON(self):
        return json.dumps(self, default=lambda o: o.__dict__, 
            sort_keys=True, indent=4)
        
    def getValue(self,v):
        if (hasattr(v, "asJSON")):
            return v.asJSON()
        elif type(v) is dict:
            return self.reprDict(v)
        elif type(v) is list:
            vlist=[]
            for vitem in v:
                vlist.append(self.getValue(vitem))
            return vlist
        else:   
            return v
    
    def reprDict(self,srcDict):
        '''
        get my dict elements
        '''
        d = dict()
        for a, v in srcDict.items():
            d[a]=self.getValue(v)
        return d
    
    def asJSON(self):
        '''
        recursively return my dict elements
        '''
        return self.reprDict(self.__dict__)   

Veți găsi aceste abordări acum integrate în proiectul https://github.com/WolfgangFahl/pyLoDStorage care este disponibil la https://pypi.org/project/pylodstorage/

person Wolfgang Fahl    schedule 03.09.2020

jsonweb pare a fi cea mai bună soluție pentru mine. Consultați 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"}'
person matthewlent    schedule 07.10.2014
comment
Funcționează bine pentru obiectele imbricate? Inclusiv decodare și codificare - person Simone Zandara; 22.12.2015

Bazându-se pe Quinten Cabo răspuns:

def sterilize(obj):
    """Make an object more ameniable to dumping as json
    """
    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()}
    list_ret = []
    dict_ret = {}
    for a in dir(obj):
        if a == '__iter__' and callable(obj.__iter__):
            list_ret.extend([sterilize(v) for v in obj])
        elif a == '__dict__':
            dict_ret.update({k: sterilize(v) for k, v in obj.__dict__.items() if k not in ['__module__', '__dict__', '__weakref__', '__doc__']})
        elif a not in ['__doc__', '__module__']:
            aval = getattr(obj, a)
            if type(aval) in (str, float, int, bool, type(None)):
                dict_ret[a] = aval
            elif a != '__class__' and a != '__objclass__' and isinstance(aval, type):
                dict_ret[a] = sterilize(aval)
    if len(list_ret) == 0:
        if len(dict_ret) == 0:
            return repr(obj)
        return dict_ret
    else:
        if len(dict_ret) == 0:
            return list_ret
    return (list_ret, dict_ret)

Diferentele sunt

  1. Funcționează pentru orice iterabil în loc de doar list și tuple (funcționează pentru matrice NumPy etc.)
  2. Funcționează pentru tipurile dinamice (cele care conțin un __dict__).
  3. Include tipurile native float și None, astfel încât acestea să nu fie convertite în șir.
  4. Clasele care au __dict__ și membri vor funcționa în mare parte (dacă numele __dict__ și membrii se ciocnesc, veți obține doar unul - probabil membrul)
  5. Clasele care sunt liste și au membri vor arăta ca un tuplu al listei și un dicționar
  6. Python3 (apelul isinstance() ar putea să fie singurul lucru care trebuie schimbat)
person mheyman    schedule 02.05.2020

Cel mai mult mi-a plăcut metoda lui Lost Koder. Am întâmpinat probleme când am încercat să serializez obiecte mai complexe ale căror membri/metode nu sunt serializabile. Iată implementarea 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)
person Will Charlton    schedule 11.11.2017

Am întâlnit această problemă când am încercat să stochez modelul lui Peewee în PostgreSQL JSONField.

După ce m-am chinuit o vreme, iată soluția generală.

Cheia soluției mele este să parcurg codul sursă al lui Python și să realizez că documentația codului (descrisă aici) explică deja cum să extindeți json.dumps existent pentru a accepta alte tipuri de date.

Să presupunem că aveți în prezent un model care conține unele câmpuri care nu sunt serializabile la JSON și modelul care conține câmpul JSON inițial arată astfel:

class SomeClass(Model):
    json_field = JSONField()

Doar definiți un JSONEncoder personalizat 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 folosește-l în JSONField ca mai jos:

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

Cheia este metoda default(self, obj) de mai sus. Pentru fiecare reclamație ... is not JSON serializable pe care o primiți de la Python, trebuie doar să adăugați cod pentru a gestiona tipul neserializat în JSON (cum ar fi Enum sau datetime)

De exemplu, iată cum accept o clasă care 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 model Peewee într-un obiect seriazabil JSON, 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) specific lui Peewee, dar cred că:

  1. Este aplicabil altor ORM-uri (Django, etc) în general
  2. De asemenea, dacă ați înțeles cum funcționează json.dumps, această soluție funcționează și cu Python (fără ORM) în general și

Orice întrebări, vă rugăm să postați în secțiunea de comentarii. Mulțumiri!

person sivabudh    schedule 30.07.2018

Mai întâi trebuie să facem obiectul nostru compatibil cu JSON, astfel încât să îl putem arunca folosind modulul JSON standard. Am facut asa:

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
person Adi Degani    schedule 27.02.2020

Această funcție folosește recursiunea pentru a itera fiecare parte a dicționarului și apoi apelează metodele repr() ale claselor care nu sunt tipuri încorporate.

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, float):
        return obj
    else:
        return obj.__repr__()
person Quinten Cabo    schedule 30.03.2020

Am venit cu soluția mea. Utilizați această metodă, transmiteți orice document (dict,list, ObjectId etc) pentru a serializa.

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
person Dewsworld    schedule 21.05.2015

Dacă puteți instala un pachet, vă recomand să încercați dill, care a funcționat foarte bine pentru proiectul meu. Un lucru frumos despre acest pachet este că are aceeași interfață ca pickle, așa că dacă ați folosit deja pickle în proiectul dvs., puteți pur și simplu să înlocuiți în dill și să vedeți dacă scriptul rulează, fără a schimba niciun cod. Deci este o soluție foarte ieftină de încercat!

(Anti-dezvăluire completă: nu sunt în niciun fel afiliat și nu am contribuit niciodată la proiectul Dill.)

Instalează pachetul:

pip install dill

Apoi editați codul pentru a importa dill în loc de pickle:

# import pickle
import dill as pickle

Rulați scriptul și vedeți dacă funcționează. (Dacă se întâmplă, poate doriți să curățați codul, astfel încât să nu mai umbriți numele modulului pickle!)

Câteva detalii despre tipurile de date pe care dill le pot și nu se serializa, de pe pagina proiectului:

dill poate mura următoarele tipuri standard:

niciunul, tip, bool, int, lung, float, complex, str, unicode, tuplu, listă, dict, fișier, buffer, încorporat, atât clase de stil vechi cât și noi, instanțe de clase de stil vechi și noi, set, frozenset, matrice , funcții, excepții

dill poate mura și tipuri standard mai „exotice”:

funcții cu randamente, funcții imbricate, lambdas, celulă, metodă, unboundmethod, modul, cod, methodwrapper, dictproxy, methoddescriptor, getsetdescriptor, memberdescriptor, wrapperdescriptor, xrange, slice, notimplemented, elipse, ieșire

dill nu poate încă mura aceste tipuri standard:

cadru, generator, traceback

person thedavidmo    schedule 18.12.2018

Nu văd aici nicio mențiune despre versiunea în serie sau backcompat, așa că voi posta soluția mea pe care o folosesc de ceva vreme. Probabil că am mult mai multe de învățat, în special Java și Javascript sunt probabil mai maturi decât mine aici, dar aici merge

https://gist.github.com/andy-d/b7878d0044a4244a4244a42420cfea>98d50cfe4

person Fletch F Fletch    schedule 27.08.2019

Pentru a adăuga o altă opțiune: Puteți utiliza pachetul attrs și metoda asdict.

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

json.dumps(objects, cls=ObjectEncoder)

și pentru a converti înapoi

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)

clasa arata asa

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

În plus față de răspunsul lui Onur, probabil doriți să vă ocupați de tipul de dată și oră ca mai jos.
(pentru a gestiona : obiectul „datetime.datetime” nu are excepția atributului „dict”.

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

Utilizare:

def toJSON(self):
    return json.dumps(self, default=datetime_option, sort_keys=True, indent=4)
person Mark Choi    schedule 03.02.2020

Aceasta este o bibliotecă mică care serializează un obiect cu toți copiii săi în JSON și, de asemenea, îl analizează înapoi:

https://github.com/tobiasholler/PyJSONSerialization/

person Tobi    schedule 17.07.2014

Există multe abordări ale acestei probleme. „ObjDict” (pip install objdict) este altul. Se pune accent pe furnizarea de obiecte asemănătoare javascript care pot acționa și ca dicționare pentru a gestiona cel mai bine datele încărcate din JSON, dar există și alte caracteristici care pot fi utile. Aceasta oferă o altă soluție alternativă la problema inițială.

person innov8    schedule 02.10.2016

Am ales să folosesc decoratori pentru a rezolva problema serializării obiectelor datetime. Iată codul meu:

#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

Prin importul modulului de mai sus, celelalte module ale mele folosesc json într-un mod normal (fără a specifica cuvântul cheie implicit) pentru a serializa datele care conțin obiecte date și oră. Codul de serializare datetime este apelat automat pentru json.dumps și json.dump.

person John Moore    schedule 16.07.2017

person    schedule
comment
Din doc: parametrul default(obj) este o funcție care ar trebui să returneze o versiune serializabilă a obj sau raise TypeError. default implicit generează pur și simplu TypeError. - person luckydonald; 28.06.2016