Jak sprawić, by klasa JSON mogła być serializowana

Jak sprawić, by klasa Pythona mogła być serializowana?

Prosta klasa:

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

Co powinienem zrobić, aby móc uzyskać dane wyjściowe:

>>> import json

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

Bez błędu


person Sergey    schedule 22.09.2010    source źródło
comment
Szkoda, że ​​wszystkie odpowiedzi wydają się odpowiadać na pytanie: Jak serializować klasę? zamiast pytania akcji. Jak sprawić, by klasa mogła być serializowana? Odpowiedzi te zakładają, że sam wykonujesz serializację, zamiast przekazywać obiekt do innego modułu, który go serializuje.   -  person Kyle Delaney    schedule 18.10.2019
comment
Jeśli używasz Pythona 3.5+, możesz użyć jsons. Przekonwertuje Twój obiekt (i wszystkie jego atrybuty rekurencyjnie) na plik dyktowany. import jsons zobacz odpowiedź poniżej — działa doskonale   -  person tswaehn    schedule 02.04.2020
comment
@KyleDelaney Naprawdę liczyłem na interfejs/magiczną metodę, którą mógłbym zaimplementować, aby również można było go searializować. Chyba będę musiał zaimplementować funkcję .to_dict() lub coś, co można wywołać na obiekcie, zanim zostanie on przekazany do modułu, który spróbuje go serializować.   -  person Felix B.    schedule 01.09.2020
comment
zobacz stackoverflow.com/a/63718624/1497139, aby rozpocząć miksowanie JSONable   -  person Wolfgang Fahl    schedule 03.09.2020
comment
@FelixB. Możesz użyć wbudowanej funkcji vars w połączeniu z json.dumps (zobacz moją odpowiedź stackoverflow.com/a/64469761/1587520 )   -  person user1587520    schedule 23.11.2020


Odpowiedzi (33)


Czy masz pojęcie o oczekiwanych wynikach? Na przykład, czy to wystarczy?

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

W takim przypadku możesz po prostu zadzwonić pod numer json.dumps(f.__dict__).

Jeśli chcesz bardziej spersonalizowanych wyników, będziesz musiał podklasę JSONEncoder i zaimplementować własną, niestandardową serializację.

Banalny przykład znajdziesz poniżej.

>>> from json import JSONEncoder
>>> class MyEncoder(JSONEncoder):
        def default(self, o):
            return o.__dict__    

>>> MyEncoder().encode(f)
'{"fname": "/foo/bar"}'

Następnie przekazujesz tę klasę do metody json.dumps() jako cls kwarg:

json.dumps(cls=MyEncoder)

Jeśli chcesz także dekodować, musisz podać niestandardowy object_hook w JSONDecoder klasa. Na przykład:

>>> 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
Użycie __dict__ nie będzie działać we wszystkich przypadkach. Jeśli atrybuty nie zostały ustawione po utworzeniu instancji obiektu, pole __dict__ może nie zostać w pełni wypełnione. W powyższym przykładzie wszystko jest w porządku, ale jeśli masz atrybuty klasy, które również chcesz zakodować, nie zostaną one wymienione w __dict__, chyba że zostały zmodyfikowane w wywołaniu klasy __init__ lub w inny sposób po zakodowaniu obiektu instancja. - person Kris Hardy; 29.12.2011
comment
+1, ale funkcja from_json() używana jako hak obiektowy powinna mieć instrukcję else: return json_object, aby mogła również radzić sobie z obiektami ogólnymi. - person jogojapan; 19.03.2013
comment
@KrisHardy __dict__ również nie działa, jeśli użyjesz __slots__ w nowej klasie stylu. - person badp; 13.12.2013
comment
Możesz użyć niestandardowego JSONEncoder jak powyżej, aby utworzyć niestandardowy protokół, na przykład sprawdzić istnienie metody __json_serializable__ i wywołać ją w celu uzyskania serializowalnej reprezentacji obiektu JSON. Byłoby to zgodne z innymi wzorcami Pythona, takimi jak __getitem__, __str__, __eq__ i __len__. - person jpmc26; 15.07.2015
comment
jestem nowy w Pythonie... czy możemy używać tych bibliotek (json lub simplejson) do serializacji/deserializacji obiektów, które są szeroko używane przez programistów Pythona, np. ramki danych pandy, serie itp.? - person Mahesha999; 20.09.2016
comment
__dict__ również nie będzie działać rekurencyjnie, np. jeśli atrybut Twojego obiektu jest innym obiektem. - person Neel; 10.04.2018
comment
JSONEncoder wywołaj metodę default podklasy tylko wtedy, gdy podane dane są niestandardowe. - person yoonghm; 18.09.2018
comment
@Mahesha999 użyj metody df.to_json() dla ramki danych, serii itp - person user2561747; 07.12.2018

Oto proste rozwiązanie dla prostej funkcji:

.toJSON() Metoda

Zamiast klasy nadającej się do serializacji JSON, zaimplementuj metodę serializatora:

import json

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

Więc po prostu wywołujesz to, aby serializować:

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

print(me.toJSON())

wyświetli:

{
    "age": 35,
    "dog": {
        "name": "Apollo"
    },
    "name": "Onur"
}
person Onur Yıldırım    schedule 21.03.2013
comment
Bardzo limitowany. Jeśli masz dyktat {foo:bar,baz:bat}, który z łatwością będzie serializowany do JSON. Jeśli zamiast tego masz {foo:bar,baz:MyObject()}, to nie możesz. Idealną sytuacją byłoby, gdyby zagnieżdżone obiekty były serializowane do JSON rekurencyjnie, a nie jawnie. - person Mark E. Haase; 22.08.2013
comment
To nadal będzie działać. Brakuje Ci o.__dict___. Wypróbuj własny przykład: 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
Czy to rozwiązanie jest odwracalne? Tj. Czy łatwo jest zrekonstruować obiekt z jsona? - person Jorge Leitao; 26.04.2015
comment
@J.C.Leitão Nie. Możesz mieć dwie różne klasy z tymi samymi polami. Obiekty aib tej klasy (prawdopodobnie o tych samych właściwościach) miałyby takie same a.__dict__ / b.__dict__. - person Martin Thoma; 16.06.2015
comment
Jednakże prawdopodobnie mógłbyś dodać pole około unjson_class, które podpowiada, jaką klasę pierwotnie posiadał obiekt. Następnie możesz użyć introspekcji swojego modułu i użyć czegoś takiego jak stackoverflow.com/a/2169191/562769, aby odzyskać obiekt . - person Martin Thoma; 16.06.2015
comment
To nie działa z datetime.datetime instancjami. Zgłasza następujący błąd: 'datetime.datetime' object has no attribute '__dict__' - person Bruno Finger; 17.06.2015
comment
Poniższe będą działać dla wszystkiego, co nie ma __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 tak, jest to odwracalne. po załadowaniu pliku json możesz dodać każdą parę klucz/wartość do właściwości obiektu dict, zobacz stackoverflow.com/questions/6578986/ - person FistOfFury; 03.09.2017
comment
Chyba czegoś mi brakuje, ale wygląda na to, że to nie działa (tzn. json.dumps(me) nie wywołuje metody toJSON Object. - person cglacet; 24.08.2018
comment
Możesz użyć default=vars zamiast default=lambda o: o.__dict__. - person Matthew D. Scholefield; 24.02.2019
comment
@cglacet Dzieje się tak dlatego, że nie umożliwił serializacji klasy, po prostu stworzył metodę, która wypluwa ciąg JSON. To nie jest właściwa odpowiedź na pytanie, to raczej hack do specjalnych przypadków. Ale ten właściwy jest powyżej. Jeśli chcesz, aby YourObject został serializowany jako część/zawartość innego obiektu ParentObject, musisz utworzyć koder. - person Javo; 08.09.2020
comment
Pytanie dotyczy konkretnie tworzenia json.dumps(). Nie jak samodzielnie wdrożyć całą serializację JSON. - person Ernest; 23.10.2020

W przypadku bardziej złożonych klas możesz rozważyć narzędzie jsonpickle:

jsonpickle to biblioteka Pythona do serializacji i deserializacji złożonych obiektów Pythona do i z JSON.

Standardowe biblioteki Pythona do kodowania Pythona w JSON, takie jak json, simplejson i demjson biblioteki stdlib, obsługują tylko prymitywy Pythona, które mają bezpośredni odpowiednik w JSON (np. dyktatury, listy, ciągi znaków, int itp.). jsonpickle opiera się na tych bibliotekach i umożliwia serializację bardziej złożonych struktur danych do formatu JSON. Jsonpickle jest wysoce konfigurowalny i rozszerzalny — pozwala użytkownikowi wybrać backend JSON i dodać dodatkowe backendy.

(link do jsonpickle w PyPi)

person gecco    schedule 23.12.2011
comment
Pochodząc z C#, właśnie tego się spodziewałem. Prosta jedna linijka i brak bałaganu w klasach. - person Jerther; 14.12.2015
comment
jsonpickle jest niesamowity. Działało to doskonale w przypadku ogromnego, złożonego, niechlujnego obiektu z wieloma poziomami klas - person wisbucky; 04.03.2016
comment
czy istnieje przykład prawidłowego sposobu zapisania tego do pliku? Dokumentacja pokazuje tylko, jak kodować i dekodować obiekt jsonpickle. Ponadto nie było to w stanie zdekodować dyktatu zawierającego ramki danych pandy. - person user5359531; 16.08.2016
comment
@user5359531 możesz użyć obj = jsonpickle.decode(file.read()) i file.write(jsonpickle.encode(obj)). - person Kilian Batzner; 02.01.2017
comment
Pytanie specjalnie dla Django: czy użycie jsonpickle do serializacji danych sesji ma tę samą lukę w zabezpieczeniach co pickle? (zgodnie z opisem tutaj https://docs.djangoproject.com/en/1.11/topics/http/sessions/#bundled-serializers)? - person Paul Bormans; 23.06.2017
comment
Mi to pasuje!. To jest to, czego potrzebowałem. Chciałem tylko wydrukować obiekt scenariusza zachowania. - person matabares; 26.05.2020
comment
To świetne rozwiązanie. Zamiast budować złożone rozszerzenie dekodera JSON dla modułu json, udało mi się serializować moją klasę za pomocą jednej linii: s=jsonpickle.encode(self). Przywrócenie było równie proste. To jest prawdziwy duch Pythona. +1 - person NoCake; 18.09.2020

Większość odpowiedzi dotyczy zmiany wywołania funkcji json.dumps(), co nie zawsze jest możliwe lub pożądane (może się to na przykład zdarzyć wewnątrz komponentu frameworka).

Jeśli chcesz móc wywoływać json.dumps(obj) w niezmienionej postaci, prostym rozwiązaniem jest dziedziczenie z 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

Działa to, jeśli twoja klasa jest tylko podstawową reprezentacją danych, w przypadku trudniejszych rzeczy zawsze możesz jawnie ustawić klucze.

person andyhasit    schedule 03.07.2015
comment
To może być naprawdę niezłe rozwiązanie :) Myślę, że w moim przypadku tak. Korzyści: komunikujesz kształt obiektu, czyniąc go klasą za pomocą init, z natury można go serializować i wygląda na interpretowalny jako repr. - person PascalVKooten; 22.09.2016
comment
Chociaż nadal brakuje dostępu do punktu :( - person PascalVKooten; 22.09.2016
comment
Ach, to wydaje się działać! Dziękuję, nie wiem, dlaczego nie jest to zaakceptowana odpowiedź. Całkowicie zgadzam się, że zmiana dumps nie jest dobrym rozwiązaniem. Nawiasem mówiąc, w większości przypadków prawdopodobnie chcesz mieć dziedziczenie dict razem z delegacją, co oznacza, że ​​będziesz mieć w swojej klasie atrybut typu dict, następnie przekażesz ten atrybut jako parametr jako inicjalizację, coś w rodzaju super().__init__(self.elements). - person cglacet; 24.08.2018
comment
W moim przypadku musiałem przechowywać dane, które były niewidoczne dla json.dumps(), więc użyłem tej metody. Klasa DictWithRider pobiera dowolny obiekt, przechowuje go jako element członkowski i udostępnia go za pomocą funkcji get_rider_obj(), ale nie przekazuje go do dict.__init__(). Zatem części aplikacji, które chcą zobaczyć ukryte dane, mogą wywołać funkcję d.get_rider_obj(), ale funkcja json.dumps() widzi w zasadzie pusty słownik. Jak wspomniał @PascalVKooten, nie można uzyskać dostępu do zwykłych członków za pomocą notacji kropkowej, ale można uzyskać dostęp do funkcji. - person gkimsey; 21.07.2020
comment
do prostego użycia jest to idealne rozwiązanie. notację kropkową można łatwo włączyć za pomocą dodatkowych linii po dict.__init__( linii jako self.fname = fname, a obiekt można deserializować za pomocą f = FileItem(**json.loads(serialised_f)) - person paddyg; 21.01.2021
comment
Bardzo lubię ten model za jego prostotę. Musisz całkowicie zaprojektować swoje klasy (bez łatania małp), ale działa to naprawdę dobrze, nawet jeśli członkami twojej klasy są listy obiektów. - person Cerno; 03.05.2021
comment
6 lat później jest to nadal najlepsza odpowiedź - person rossco; 09.07.2021
comment
Właśnie tego mi potrzeba, dziękuję bardzo - person Qingyi Wu; 19.07.2021

Podoba mi się odpowiedź Onura, ale rozszerzyłbym ją o opcjonalną metodę toJSON() dla obiektów do serializacji:

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
Uważam, że jest to najlepsza równowaga pomiędzy wykorzystaniem istniejącego json.dumps a wprowadzeniem niestandardowej obsługi. Dzięki! - person Daniel Buckmaster; 15.04.2015
comment
Właściwie to bardzo mi się podoba; ale zamiast try-catch prawdopodobnie zrobiłby coś takiego jak if 'toJSON' in obj.__attrs__():..., aby uniknąć cichej awarii (w przypadku niepowodzenia w toJSON() z innego powodu niż jego brak)... awarii, która potencjalnie prowadzi do uszkodzenia danych. - person thclark; 22.11.2017
comment
@thclark, jak rozumiem, idomatyczny python prosi o przebaczenie, a nie o pozwolenie, więc try-except jest właściwym podejściem, ale należy przechwycić poprawny wyjątek, w tym przypadku AttributeError. - person Phil; 08.09.2020
comment
@phil, teraz kilka lat starszy i mądrzejszy, zgodziłbym się z tobą. - person thclark; 08.09.2020
comment
To naprawdę powinno wyraźnie przechwytywać AttributeError - person juanpa.arrivillaga; 04.02.2021
comment
A co jeśli AttributeError zostanie podniesione wewnątrz obj.toJSON()? - person artm; 16.05.2021

Po prostu dodaj metodę to_json do swojej klasy w następujący sposób:

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

I dodaj ten kod (z tę odpowiedź) gdzieś na szczycie wszystkiego:

from json import JSONEncoder

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

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

Spowoduje to wprowadzenie poprawki do modułu json po jego zaimportowaniu, dzięki czemu funkcja JSONEncoder.default() automatycznie sprawdzi specjalną metodę „to_json()” i użyje jej do zakodowania obiektu, jeśli zostanie znaleziony.

Tak jak powiedział Onur, ale tym razem nie musisz aktualizować co json.dumps() w swoim projekcie.

person Fancy John    schedule 04.08.2016
comment
Wielkie dzięki! To jedyna odpowiedź, która pozwala mi robić to, co chcę: móc serializować obiekt bez zmiany istniejącego kodu. Inne metody w większości nie działają dla mnie. Obiekt jest zdefiniowany w bibliotece innej firmy, a kod serializacji również jest obcy. Ich zmiana będzie niewygodna. Dzięki Twojej metodzie wystarczy, że zrobię TheObject.to_json = my_serializer. - person Yongwei Wu; 11.10.2017

Inną opcją jest zawinięcie zrzutu JSON we własnej klasie:

import json

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

    def __repr__(self):
        return json.dumps(self.__dict__)

Lub, jeszcze lepiej, podklasa klasy FileItem z klasy 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

Testowanie:

>>> 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
Cześć, nie podoba mi się to podejście do niestandardowego kodera, byłoby lepiej, gdybyś mógł sprawić, że twoja klasa json będzie możliwa do serializacji. Próbuję, próbuję, próbuję i nic. Czy jest jakiś pomysł jak to zrobić. Rzecz w tym, że moduł json testuje twoją klasę pod kątem wbudowanych typów Pythona, a nawet mówi, że dla niestandardowych klas utwórz swój koder :). Czy można to sfałszować? Więc mógłbym coś zrobić z moją klasą, aby zachowywała się jak prosta lista w module json? Próbuję subclasscheck i instancecheck, ale nic. - person Bojan Radojevic; 15.08.2012
comment
@ADRENALIN Możesz dziedziczyć z typu podstawowego (prawdopodobnie dykt), jeśli wszystkie wartości atrybutów klasy można serializować i nie masz nic przeciwko hackom. Możesz także użyć jsonpickle lub json_tricks lub czegoś innego zamiast standardowego (nadal jest to niestandardowy koder, ale nie taki, który musisz pisać ani dzwonić). Pierwsza wybiera instancję, druga przechowuje ją jako dykt atrybutów, które możesz zmienić, implementując __json__encode__ / __json_decode__ (ujawnienie: zrobiłem ostatni). - person Mark; 20.10.2016
comment
Nie sprawia to, że obiekt można serializować dla klasy json. Zapewnia tylko metodę uzyskania zwróconego ciągu JSON (trywialnego). Zatem json.dumps(f) zakończy się niepowodzeniem. Nie o to pytano. - person omni; 21.09.2020

Jak wspomniano w wielu innych odpowiedziach, możesz przekazać funkcję do json.dumps, aby przekonwertować obiekty, które nie są jednym z domyślnie obsługiwanych typów, na obsługiwany typ. Co zaskakujące, żaden z nich nie wspomina o najprostszym przypadku, jakim jest użycie wbudowanej funkcji vars, aby przekonwertować obiekty na plik zawierający wszystkie ich atrybuty:

json.dumps(obj, default=vars)

Pamiętaj, że dotyczy to tylko podstawowych przypadków, jeśli potrzebujesz bardziej szczegółowej serializacji dla niektórych typów (np. Wykluczając pewne atrybuty lub obiekty, które nie mają atrybutu __dict__), musisz użyć funkcji niestandardowej lub JSONEncoder, jak opisano w innych odpowiedziach .

person user1587520    schedule 21.10.2020
comment
nie jest jasne, co rozumiesz przez default=vars, czy to oznacza, że ​​vars jest domyślnym serializatorem? Jeśli nie: To tak naprawdę nie rozwiązuje przypadku, w którym nie możesz wpływać na sposób wywoływania json.dumps. Jeśli po prostu przekażesz obiekt do biblioteki, a ta biblioteka wywoła json.dumps na tym obiekcie, tak naprawdę nie pomoże to, że zaimplementowałeś vars, jeśli ta biblioteka nie używa dumps w ten sposób. W tym sensie jest to odpowiednik niestandardowego JSONEncoder. - person Felix B.; 24.11.2020
comment
Masz rację, to nic innego jak prosty wybór niestandardowego serializatora i nie rozwiązuje opisanego przypadku. Jeśli dobrze widzę, nie ma rozwiązania w tym przypadku, w którym nie kontrolujesz sposobu wywoływania json.dumps. - person user1587520; 25.11.2020
comment
Rozwiązałem moją sprawę. Po prostu genialne! - person Rub; 02.12.2020
comment
W przypadku niektórych obiektów takie podejście spowoduje wyrzucenie vars() argument must have __dict__ attribute - person JustAMartin; 24.02.2021

Któregoś dnia natknąłem się na ten problem i zaimplementowałem bardziej ogólną wersję Encodera dla obiektów Pythona, który może obsłużyć obiekty zagnieżdżone i odziedziczone pola:

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

Przykład:

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)

Wynik:

{
  "a": "A", 
  "b": [
    {
      "ab": {
        "b": "B", 
        "i": "I", 
        "y": "y"
      }
    }
  ], 
  "c": {
    "c": "YES"
  }, 
  "i": "I"
}
person tobigue    schedule 18.02.2016
comment
Chociaż jest to trochę stare. Mam do czynienia z błędem importu cyklicznego. Więc zamiast return obj w ostatniej linii zrobiłem to return super(ObjectEncoder, self).default(obj). Odniesienie TUTAJ - person SomeTypeFoo; 11.04.2017

Jeśli używasz Pythona 3.5 lub nowszego, możesz użyć jsons. (PyPi: https://pypi.org/project/jsons/) Spowoduje to konwersję Twojego obiektu (i wszystkie jego atrybuty rekurencyjnie) do dyktatu.

import jsons

a_dict = jsons.dump(your_object)

Lub jeśli chcesz ciąg:

a_str = jsons.dumps(your_object)

Lub jeśli twoja klasa zaimplementowała jsons.JsonSerializable:

a_dict = your_object.json
person R H    schedule 19.12.2018
comment
Jeśli potrafisz używać Pythona w wersji 3.7 lub nowszej, odkryłem, że najczystszym rozwiązaniem do konwersji klas Pythona na dyktatury i ciągi JSON (i odwrotnie) jest zmieszanie biblioteki jsons z klasy danych. Póki co, dla mnie bardzo dobrze! - person Ruluk; 26.02.2019
comment
Jest to biblioteka zewnętrzna, która nie jest wbudowana w standardową instalację Pythona. - person Noumenon; 10.07.2019
comment
tylko dla klasy, która ma atrybut sloty - person yehudahs; 03.12.2019
comment
Możesz, ale nie musisz używać miejsc. Tylko przy zrzucaniu zgodnie z sygnaturą konkretnej klasy będziesz potrzebować slotów. W nadchodzącej wersji 1.1.0 już tak nie jest. - 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]')))

jeśli używasz standardu json, musisz zdefiniować funkcję 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
Uprościłem to, usuwając funkcję _asdict z lambdą json.dumps(User('alice', '[email protected]'), default=lambda x: x.__dict__) - person JustEngland; 29.11.2018

json jest ograniczony pod względem obiektów, które może wydrukować, a jsonpickle (możesz potrzebować pip install jsonpickle) jest ograniczony pod względem braku możliwości wcięcia tekstu. Jeśli chcesz sprawdzić zawartość obiektu, którego klasy nie możesz zmienić, nadal nie mogę znaleźć prostszego sposobu niż:

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

Uwaga: nadal nie mogą drukować metod obiektowych.

person ribamar    schedule 04.04.2016

Ta klasa może załatwić sprawę, konwertuje obiekt na standardowy json .

import json


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

stosowanie:

Serializer.serialize(my_object)

pracuje w python2.7 i python3.

person Lost Koder    schedule 09.10.2016
comment
Ta metoda podobała mi się najbardziej. Podczas próby serializacji bardziej złożonych obiektów, których elementy/metody nie nadają się do serializacji, napotkałem problemy. Oto moja implementacja, która działa na większej liczbie obiektów: ``` class Serializer(obiekt): @staticmethod def serialize(obj): def check(o): for k, v in o.__dict__.items(): try: _ = json .dumps(v) o.__dict__[k] = v z wyjątkiem TypeError: o.__dict__[k] = str(v) return o return json.dumps(check(obj).__dict__, indent=2) ``` - person Will Charlton; 11.11.2017

Oto moje 3 centy...
To demonstruje wyraźną serializację JSON dla obiektu Pythona przypominającego drzewo.
Uwaga: jeśli rzeczywiście potrzebujesz takiego kodu, możesz użyć skręcona klasa FilePath.

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 udzielił całkiem fajnej odpowiedzi. Musiałem naprawić kilka drobnych rzeczy, ale to działa:

Kod

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

Pamiętaj, że do załadowania potrzebne są dwa kroki. Na razie właściwość __python__ nie jest używana.

Jak powszechne jest to zjawisko?

Korzystając z metody AlJohri sprawdzam popularność podejść:

Serializacja (Python -> JSON):

  • to_json: 266 595 w dniu 27.06.2018 r.
  • toJSON: 96 307 w dniu 27.06.2018 r.
  • __json__: 8504 w dniu 27.06.2018 r.
  • for_json: 6937 w dniu 2018-06 -27

Deserializacja (JSON -> Python):

person Martin Thoma    schedule 27.06.2018

To zadziałało dobrze dla mnie:

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 wtedy

class FileItem(JsonSerializable):
    ...

I

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

Jeśli nie masz nic przeciwko zainstalowaniu pakietu, możesz użyć json-tricks:

pip install json-tricks

Następnie wystarczy zaimportować dump(s) z json_tricks zamiast json i zwykle będzie działać:

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

co da

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

I to w zasadzie tyle!


Ogólnie będzie to działać świetnie. Istnieją pewne wyjątki, np. jeśli w __new__ wydarzy się coś szczególnego lub będzie się działo więcej magii metaklasowej.

Oczywiście ładowanie również działa (w przeciwnym razie jaki jest sens):

from json_tricks import loads
json_str = loads(json_str)

Zakłada się, że module_name.test_class.MyTestCls można zaimportować i nie zmieniono go w niezgodny sposób. Zwrócisz instancję, a nie jakiś słownik czy coś, i powinna to być identyczna kopia z tą, którą porzuciłeś.

Jeśli chcesz dostosować sposób, w jaki coś jest (de)serializowane, możesz dodać do swojej klasy specjalne metody, na przykład:

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

który na przykład serializuje tylko część parametrów atrybutów.

Jako darmowy bonus otrzymujesz (de)serializację tablic numpy, datę i godzinę, uporządkowane mapy, a także możliwość dołączania komentarzy w formacie json.

Zastrzeżenie: utworzyłem json_tricks, ponieważ miałem ten sam problem co Ty.

person Mark    schedule 10.11.2016
comment
Właśnie przetestowałem json_tricks i zadziałało upiększanie (w 2019 r.). - person pauljohn32; 06.11.2019

Komentarz Kyle'a Delaneya jest poprawny, więc próbowałem użyć odpowiedzi https://stackoverflow.com/a/15538391/1497139 a także ulepszoną wersję

aby utworzyć miks JSONable.

Aby więc umożliwić serializację klasy JSON, użyj JSONable jako superklasy i albo wywołaj:

 instance.toJSON()

or

 instance.asJSON()

dla dwóch oferowanych metod. Możesz także rozszerzyć klasę JSONable o inne oferowane tutaj podejścia.

Przykładowy test jednostkowy z próbką rodziny i osoby daje w wyniku:

doJSON():

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

jako JSON():

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

Test jednostkowy z próbką rodziny i osoby

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 definiujący miks 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__)   

Te podejścia znajdziesz teraz zintegrowane w projekcie https://github.com/WolfgangFahl/pyLoDStorage który jest dostępny pod adresem https://pypi.org/project/pylodstorage/

person Wolfgang Fahl    schedule 03.09.2020

jsonweb wydaje mi się najlepszym rozwiązaniem. Zobacz 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
Czy to działa dobrze w przypadku obiektów zagnieżdżonych? Łącznie z dekodowaniem i kodowaniem - person Simone Zandara; 22.12.2015

Opierając się na Quinten Cabo odpowiedź:

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)

Różnice są

  1. Działa dla dowolnej iteracji zamiast tylko list i tuple (działa dla tablic NumPy itp.)
  2. Działa dla typów dynamicznych (tych, które zawierają __dict__).
  3. Zawiera typy natywne float i None, dzięki czemu nie są konwertowane na ciąg.
  4. Klasy, które mają __dict__ i członków, będą przeważnie działać (jeśli __dict__ i nazwy członków kolidują, otrzymasz tylko jednego - prawdopodobnie członka)
  5. Klasy będące listami i posiadające elementy będą wyglądać jak krotka listy i słownik
  6. Python3 (to isinstance() wywołanie może być jedyną rzeczą, która wymaga zmiany)
person mheyman    schedule 02.05.2020

Najbardziej podobała mi się metoda Lost Kodera. Podczas próby serializacji bardziej złożonych obiektów, których elementy/metody nie nadają się do serializacji, napotkałem problemy. Oto moja implementacja, która działa na większej liczbie obiektów:

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

Napotkałem ten problem, gdy próbowałem zapisać model Peewee w PostgreSQL JSONField.

Po chwili zmagań oto ogólne rozwiązanie.

Kluczem do mojego rozwiązania jest przejrzenie kodu źródłowego Pythona i uświadomienie sobie, że dokumentacja kodu (opisana tutaj) już wyjaśnia, jak rozszerzyć istniejący json.dumps, aby obsługiwał inne typy danych.

Załóżmy, że masz model zawierający pola, których nie można serializować do formatu JSON, a model zawierający pole JSON oryginalnie wygląda następująco:

class SomeClass(Model):
    json_field = JSONField()

Po prostu zdefiniuj niestandardowy JSONEncoder w ten sposób:

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)

A potem po prostu użyj go w swoim JSONField jak poniżej:

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

Kluczem jest powyższa metoda default(self, obj). Do każdej pojedynczej skargi ... is not JSON serializable otrzymanej od Pythona po prostu dodaj kod obsługujący typ niemożliwy do serializacji do JSON (taki jak Enum lub datetime)

Na przykład oto jak obsługuję klasę dziedziczącą z 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)

Wreszcie, po zaimplementowaniu kodu jak powyżej, możesz po prostu przekonwertować dowolne modele Peewee na obiekt nadający się do szeregowania w JSON, jak poniżej:

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

Chociaż powyższy kod był (w pewnym stopniu) specyficzny dla Peewee, ale myślę:

  1. Ogólnie ma to zastosowanie do innych ORM (Django itp.).
  2. Ponadto, jeśli zrozumiałeś, jak działa json.dumps, to rozwiązanie działa również ogólnie z Pythonem (bez ORM)

Wszelkie pytania proszę pisać w sekcji komentarzy. Dzięki!

person sivabudh    schedule 30.07.2018

Najpierw musimy sprawić, aby nasz obiekt był zgodny z JSON, abyśmy mogli go zrzucić za pomocą standardowego modułu JSON. Zrobiłem to w ten sposób:

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

Ta funkcja używa rekurencji do iteracji po każdej części słownika, a następnie wywołuje metody repr() klas, które nie są typami wbudowanymi.

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

Wymyśliłem własne rozwiązanie. Użyj tej metody, aby przekazać dowolny dokument (dict, list, ObjectId itp.) do serializacji.

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

Jeśli możesz zainstalować pakiet, polecam wypróbować dill, który działał dobrze mój projekt. Dobrą rzeczą w tym pakiecie jest to, że ma ten sam interfejs co pickle, więc jeśli już korzystałeś z pickle w swoim projekcie, możesz po prostu zastąpić dill i sprawdzić, czy skrypt się uruchomi, bez zmiany żadnego kodu. Jest to więc bardzo tanie rozwiązanie do wypróbowania!

(Pełne zabezpieczenie przed ujawnieniem informacji: nie jestem w żaden sposób powiązany z projektem dill ani nigdy nie brałem w nim udziału.)

Zainstaluj pakiet:

pip install dill

Następnie edytuj swój kod, aby zaimportować dill zamiast pickle:

# import pickle
import dill as pickle

Uruchom swój skrypt i sprawdź, czy działa. (Jeśli tak, możesz wyczyścić swój kod, aby nie przesłaniać już nazwy modułu pickle!)

Kilka szczegółów na temat typów danych, które dill można i nie można serializować, można znaleźć na stronie projektu:

dill może marynować następujące standardowe rodzaje:

brak, typ, bool, int, długi, float, złożony, str, unicode, krotka, lista, dyktowanie, plik, bufor, wbudowane, klasy w starym i nowym stylu, instancje klas w starym i nowym stylu, zestaw, zamrożony zestaw, tablica , funkcje, wyjątki

dill może również marynować bardziej „egzotyczne” standardowe typy:

funkcje z wynikami, funkcje zagnieżdżone, lambda, komórka, metoda, niezwiązana metoda, moduł, kod, methodwrapper, dictproxy, deskryptor metody, getsetdescriptor, memberdescriptor, wrapperdescriptor, xrange, plasterek, niezaimplementowany, wielokropek, zakończ

dill nie może jeszcze marynować tych standardowych typów:

ramka, generator, śledzenie

person thedavidmo    schedule 18.12.2018

Nie widzę tutaj żadnej wzmianki o wersjonowaniu szeregowym ani o kompatybilności wstecznej, więc opublikuję moje rozwiązanie, którego używam od jakiegoś czasu. Prawdopodobnie mam o wiele więcej do nauczenia się, szczególnie Java i JavaScript są prawdopodobnie bardziej dojrzałe ode mnie, ale proszę bardzo

https://Gist.github.com/andy-d/b7878d0044a4242c0498ed6d67fd50fe

person Fletch F Fletch    schedule 27.08.2019

Aby dodać kolejną opcję: Możesz użyć pakietu attrs i metody asdict.

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

json.dumps(objects, cls=ObjectEncoder)

i nawrócić się

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)

zajęcia wyglądają tak

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

Oprócz odpowiedzi Onura, prawdopodobnie chcesz zająć się typem datetime jak poniżej.
(aby obsłużyć : obiekt „datetime.datetime” nie ma wyjątku atrybutu „dict”.)

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

Stosowanie:

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

To mała biblioteka, która serializuje obiekt ze wszystkimi jego dziećmi do formatu JSON, a także analizuje go ponownie:

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

person Tobi    schedule 17.07.2014

Istnieje wiele podejść do tego problemu. „ObjDict” (pip install objdict) to kolejny. Nacisk kładziony jest na dostarczanie obiektów przypominających JavaScript, które mogą również działać jak słowniki, aby jak najlepiej obsługiwać dane ładowane z JSON, ale są też inne funkcje, które mogą być również przydatne. Zapewnia to kolejne alternatywne rozwiązanie pierwotnego problemu.

person innov8    schedule 02.10.2016

Zdecydowałem się użyć dekoratorów, aby rozwiązać problem serializacji obiektu datetime. Oto mój kod:

#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

Importując powyższy moduł, inne moje moduły używają json w normalny sposób (bez określania domyślnego słowa kluczowego) do serializacji danych zawierających obiekty daty i godziny. Kod serializatora datetime jest automatycznie wywoływany dla json.dumps i json.dump.

person John Moore    schedule 16.07.2017

person    schedule
comment
Z doc: Parametr default(obj) to funkcja, która powinna zwrócić serializowalna wersja obj lub raise TypeError. Domyślny default po prostu wywołuje TypeError. - person luckydonald; 28.06.2016