Jak uczynić klasę JSON serializowalną

833

Jak uczynić klasę Python serializowalną?

Prosta klasa:

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

Co powinienem zrobić, aby uzyskać wynik:

>>> import json

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

Bez błędu

Siergiej
źródło
30
To niefortunne, że wszystkie odpowiedzi wydają się odpowiadać na pytanie „Jak serializować klasę?” zamiast pytania akcji „Jak sprawić, by klasa była serializowalna?” W tych odpowiedziach założono, że serializację wykonuje się samodzielnie, zamiast przekazywać obiekt do innego modułu, który serializuje go.
Kyle Delaney,
Jeśli używasz Python3.5 +, możesz użyć jsons. Konwertuje Twój obiekt (i wszystkie jego atrybuty rekurencyjnie ) na dyktando. import jsonspatrz odpowiedź poniżej - działa idealnie
tswaehn

Odpowiedzi:

551

Czy masz pojęcie o oczekiwanej wydajności? Na przykład czy to zrobi?

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

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

Jeśli chcesz uzyskać bardziej spersonalizowane dane wyjściowe, będziesz musiał podklasować JSONEncoderi wdrożyć własną niestandardową serializację.

Trywialny przykład, patrz 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 json.dumps()metody jako clskwarg:

json.dumps(cls=MyEncoder)

Jeśli chcesz również zdekodować, musisz dostarczyć niestandardowe object_hookdo JSONDecoderklasy. 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>
>>> 
Manoj Govindan
źródło
44
Korzystanie __dict__nie będzie działać we wszystkich przypadkach. Jeśli atrybuty nie zostały ustawione po utworzeniu instancji obiektu, __dict__być może nie zostaną 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, __dict__chyba że zostały zmodyfikowane w __init__wywołaniu klasy lub w inny sposób po utworzeniu wystąpienia obiektu.
Kris Hardy
8
+1, ale from_json()funkcja użyta jako hook obiektu powinna mieć else: return json_objectinstrukcję, aby mogła również obsługiwać obiekty ogólne.
jogojapan
8
@KrisHardy __dict__również nie działa, jeśli używasz __slots__nowej klasy stylu.
badp
7
Możesz użyć niestandardowego JSONEncoderjak powyżej, aby utworzyć niestandardowy protokół, na przykład sprawdzając istnienie __json_serializable__metody i wywołując ją w celu uzyskania reprezentatywnej reprezentacji obiektu przez JSON. Byłoby to zgodne z innymi wzorcami Python, jak __getitem__, __str__, __eq__, i __len__.
jpmc26
5
__dict__również nie będzie działał rekurencyjnie, np. jeśli atrybut twojego obiektu jest innym obiektem.
Neel,
634

Oto proste rozwiązanie prostej funkcji:

.toJSON() metoda

Zamiast klasy szeregowalnej 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 zadzwoń do serializacji:

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"
}
Onur Yıldırım
źródło
82
Bardzo limitowany. Jeśli masz dykta {„foo”: „bar”, „baz”: „bat”}, łatwo zserializujesz je do JSON. Jeśli zamiast tego masz {„foo”: „bar”, „baz”: MyObject ()}, nie możesz. Idealna sytuacja byłaby taka, że ​​zagnieżdżone obiekty są serializowane do JSON rekurencyjnie, a nie jawnie.
Mark E. Haase
30
Nadal będzie działać. Tracisz o.__dict___. Spróbuj własnego przykładu: class MyObject(): def __init__(self): self.prop = 1 j = json.dumps({ "foo": "bar", "baz": MyObject() }, default=lambda o: o.__dict__)
Onur Yıldırım
14
Czy to rozwiązanie jest odwracalne? Czy łatwo jest zrekonstruować obiekt z JSona?
Jorge Leitao,
2
@ JCLeitã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__.
Martin Thoma,
7
To nie działa z datetime.datetimeinstancjami. Zgłasza następujący błąd:'datetime.datetime' object has no attribute '__dict__'
Bruno Finger
171

W przypadku bardziej złożonych klas można rozważyć użycie narzędzia 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 jd stdlib, simplejson i demjson, mogą obsługiwać tylko prymitywy Pythona, które mają bezpośredni odpowiednik JSON (np. Dykty, listy, ciągi znaków, ints itp.). jsonpickle buduje na tych bibliotekach i umożliwia serializację bardziej złożonych struktur danych do JSON. jsonpickle jest wysoce konfigurowalny i rozszerzalny - pozwala użytkownikowi wybrać backend JSON i dodać dodatkowe backendy.

(link do jsonpickle na PyPi)

gecco
źródło
31
Pochodzę z C #, tego się spodziewałem. Prosty liniowiec i bez bałaganu w klasach.
Jerther,
2
jsonpickle jest niesamowity. Działa idealnie dla ogromnego, złożonego, niechlujnego obiektu z wieloma poziomami klas
mądry
czy istnieje przykład właściwego sposobu zapisania tego w pliku? Dokumentacja pokazuje tylko, jak zakodować i zdekodować jsonpickleobiekt. Ponadto nie było w stanie dekodować dykta zawierającego ramki danych pand.
user5359531,
3
@ user5359531 możesz używać obj = jsonpickle.decode(file.read())i file.write(jsonpickle.encode(obj)).
Kilian Batzner
1
Pytanie specjalnie dla django: czy użycie jsonpickle do serializacji danych sesji ma taką samą podatność jak marynata? (jak opisano tutaj docs.djangoproject.com/en/1.11/topics/http/sessions/… )?
Paul Bormans
89

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

Jeśli chcesz mieć możliwość wywoływania json.dumps (obj) w obecnej postaci, to proste rozwiązanie dziedziczy po 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 po prostu podstawową reprezentacją danych, dla trudniejszych rzeczy zawsze możesz ustawić klucze jawnie.

andyhasit
źródło
2
To może być naprawdę fajne rozwiązanie :) Wierzę, że tak jest w moim przypadku. Korzyści: komunikujesz „kształt” obiektu, czyniąc go klasą z init, jest on z natury serializowalny i wygląda na interpretowalny jako repr .
PascalVKooten
1
Chociaż nadal brakuje „dostępu za pomocą kropki” :(
PascalVKooten
2
Ach, to wydaje się działać! Dzięki, nie jestem pewien, dlaczego nie jest to akceptowana odpowiedź. Całkowicie się zgadzam, że zmiana dumpsnie jest dobrym rozwiązaniem. Nawiasem mówiąc, w większości przypadków prawdopodobnie chcesz mieć dictdziedziczenie wraz z delegowaniem, co oznacza, że ​​będziesz mieć jakiś dictatrybut typu w swojej klasie, a następnie przekażesz ten atrybut jako parametr jako inicjalizację coś w stylu super().__init__(self.elements).
cglacet
47

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

def dumper(obj):
    try:
        return obj.toJSON()
    except:
        return obj.__dict__
print json.dumps(some_big_object, default=dumper, indent=2)
Jason S.
źródło
Okazało się, że jest to najlepsza równowaga między użyciem istniejącej json.dumpsa wprowadzeniem niestandardowej obsługi. Dzięki!
Daniel Buckmaster
12
Naprawdę bardzo to lubię; ale raczej nie try-catchzrobiłby czegoś takiego jak if 'toJSON' in obj.__attrs__():... aby uniknąć cichej awarii (w przypadku awarii funkcji toJSON () z innego powodu niż jej brak) ... awarii, która potencjalnie prowadzi do uszkodzenia danych.
thclark
39

Inną opcją jest zawinięcie zrzutu JSON we własną klasę:

import json

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

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

Lub jeszcze lepiej, podklasowanie klasy FileItem z JsonSerializableklasy:

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"}'
Paulo Freitas
źródło
2
Cześć, Naprawdę nie podoba mi się to podejście „niestandardowego kodera”, byłoby lepiej, gdybyś mógł sprawić, by Twoja klasa była seriazable. Próbuję i próbuję i próbuję i nic. Czy jest jakiś pomysł, jak to zrobić? Chodzi o to, że moduł json testuje twoją klasę pod kątem wbudowanych typów python, a nawet mówi, że dla niestandardowych klas twórz koder :). Czy można to sfałszować? Więc mógłbym zrobić coś z moją klasą, żeby działała jak prosta lista do modułu json? Staram subclasscheck i instancecheck ale nic.
Bojan Radojevic
@ ADRENALIN Możesz odziedziczyć po typie podstawowym (prawdopodobnie dict), jeśli wszystkie wartości atrybutów klasy można serializować i nie przeszkadza ci hackowanie. Możesz także użyć jsonpickle lub json_tricks lub czegoś innego niż standardowy (wciąż niestandardowy koder, ale nie taki, który musisz napisać lub wywołać). Ten pierwszy zachowuje instancję, drugi przechowuje ją jako listę atrybutów, które można zmienić, implementując __json__encode__/ __json_decode__(ujawnienie: zrobiłem ostatni).
Mark
30

Po prostu dodaj to_jsonmetodę do swojej klasy w ten sposób:

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

I dodaj ten kod (z tej odpowiedzi ) , gdzieś na górze 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 załatanie małpiego modułu json podczas importu, więc JSONEncoder.default () automatycznie sprawdza specjalną metodę „to_json ()” i używa go do kodowania obiektu, jeśli zostanie znaleziony.

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

Fantazyjny John
źródło
6
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 mi nie działają. Obiekt jest zdefiniowany w bibliotece innej firmy, a kod serializacji również jest innej firmy. Zmiana ich będzie niezręczna. Z twoją metodą muszę tylko zrobić TheObject.to_json = my_serializer.
Yongwei Wu,
24

Tego dnia natknąłem się na ten problem i zaimplementowałem bardziej ogólną wersję Enkodera dla obiektów Python, która może obsługiwać obiekty zagnieżdżone i pola dziedziczone :

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"
}
zmęczenie
źródło
1
Chociaż jest to trochę stary .. Mam do czynienia z błędem okrągłego importu. Więc zamiast return objw ostatniej linii to zrobiłem return super(ObjectEncoder, self).default(obj). Odniesienie TUTAJ
SomeTypeFoo 11.04.17
23

Jeśli używasz Python3.5 +, możesz użyć jsons. Konwertuje Twój obiekt (i wszystkie jego atrybuty rekurencyjnie) na dyktando.

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
RH
źródło
3
Jeśli jesteś w stanie używać Python 3.7+, odkryłem, że najczystszym rozwiązaniem do konwersji klas python na dykta i ciągi JSON (i viceversa) jest mieszanie jsonsbiblioteki z klasami danych . Jak dotąd, tak dobrze dla mnie!
Ruluk
3
To jest biblioteka zewnętrzna, nie wbudowana w standardową instalację Pythona.
Noumenon,
tylko dla klasy, która ma atrybut miejsc
yehudahs
Możesz, ale nie musisz używać automatów . Tylko podczas zrzutu zgodnie z podpisem konkretnej klasy będziesz potrzebował miejsc . W nadchodzącej wersji 1.1.0 już tak nie jest.
RH
11
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ć defaultfunkcję

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

print(json.dumps(User('alice', '[email protected]'), default=default))
tryer3000
źródło
2
Uprościłem to, usuwając funkcję _asdict z lambda json.dumps(User('alice', '[email protected]'), default=lambda x: x.__dict__)
JustEngland,
8

jsonjest ograniczony pod względem obiektów, które może wydrukować, i jsonpickle(być może potrzebujesz pip install jsonpickle) jest ograniczony pod względem, że nie może wciąć 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ą wydrukować metod obiektowych.

ribamar
źródło
6

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.7i python3.

Lost Koder
źródło
Najbardziej podobała mi się ta metoda. Wystąpiły problemy podczas próby serializacji bardziej złożonych obiektów, których członkowie / metody nie są serializowane. 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 oprócz TypeError: o .__ dict __ [k] = str (v) return o return json.dumps (check (obj) .__ dict__, indent = 2) ``
Will Charlton,
4
import 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)
prostokąt
źródło
Z doc : Parametr default(obj)jest funkcją, która powinna zwrócić możliwą do serializacji wersję obj lub wywołać TypeError. Domyślnie defaultpo prostu podnosi TypeError.
luckydonald
4

jaraco udzielił dość schludnej 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 potrzebujemy dwóch kroków. Na razie __python__właściwość nie jest używana.

Jak często to się dzieje?

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

Serializacja (Python -> JSON):

Deserializacja (JSON -> Python):

  • from_json: 226,101 w dniu 27 czerwca 2018 r
Martin Thoma
źródło
4

To zadziałało dla mnie dobrze:

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))
jmhostalet
źródło
3

Jeśli nie przeszkadza ci instalacja pakietu, możesz użyć sztuczek json :

pip install json-tricks

Potem wystarczy importu dump(s)z json_trickszamiast JSON, i to będzie zazwyczaj praca:

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 gruncie rzeczy!


To ogólnie będzie działać świetnie. Istnieją pewne wyjątki, np. Jeśli zdarzają się specjalne rzeczy lub dzieje się __new__więcej magii metaklasy.

Oczywiście ładowanie również działa (w przeciwnym razie o co chodzi):

from json_tricks import loads
json_str = loads(json_str)

Zakłada się, że module_name.test_class.MyTestClsmożna go zaimportować i nie zmienił się w niekompatybilny sposób. Otrzymasz instancję , a nie słownik lub coś takiego, i powinna to być identyczna kopia do tej, którą zrzuciłeś.

Jeśli chcesz dostosować sposób (de) serializacji czegoś, możesz dodać specjalne metody do swojej klasy, 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 serializuje tylko część parametrów atrybutów, jako przykład.

Jako darmowy bonus otrzymujesz (de) serializację tablic numpy, daty i godziny, uporządkowanych map, a także możliwość dołączania komentarzy do JSON.

Uwaga: Stworzyłem json_tricks , ponieważ miałem ten sam problem co ty.

znak
źródło
1
Właśnie przetestowałem json_tricks i zadziałało upiększanie (w 2019 roku).
pauljohn32,
2

jsonweb wydaje mi się najlepszym rozwiązaniem dla mnie. 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"}'
matthewlent
źródło
Czy działa dobrze dla zagnieżdżonych obiektów? W tym dekodowanie i kodowanie
Simone Zandara,
1

Oto moje 3 centy ...
To pokazuje wyraźną serializację json dla drzewiastego obiektu python.
Uwaga: Jeśli naprawdę potrzebujesz takiego kodu, możesz użyć skręconej klasy 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)
Dan Brough
źródło
1

Zetknąłem się z tym problemem, gdy próbowałem zapisać model Peewee w PostgreSQL JSONField.

Po krótkich zmaganiach 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.dumpsaby obsługiwał inne typy danych.

Załóżmy, że masz model, który zawiera niektóre pola, które nie są serializowane do JSON, a model zawierający pole JSON początkowo wygląda następująco:

class SomeClass(Model):
    json_field = JSONField()

Po prostu zdefiniuj niestandardowy JSONEncodertaki 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 JSONFieldjak poniżej:

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

Kluczem jest default(self, obj)powyższa metoda. Do każdej ... is not JSON serializableskargi otrzymywanej od Pythona wystarczy dodać kod, aby obsłużyć typ, który nie może zostać przekształcony w JSON (taki jak Enumlub datetime)

Na przykład oto, jak popieram 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, dzięki zaimplementowanemu kodowi, jak powyżej, możesz po prostu przekonwertować dowolne modele Peewee na obiekt, który może być seriazowalny 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ł (nieco) specyficzny dla Peewee, ale myślę:

  1. Dotyczy to ogólnie innych ORM (Django itp.)
  2. Ponadto, jeśli rozumiesz, jak to json.dumpsdziała, to rozwiązanie działa również ogólnie z Pythonem (bez ORM)

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

sivabudh
źródło
1

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):
        return obj
    else:
        return obj.__repr__()
Quinten Cabo
źródło
0

Wymyśliłem własne rozwiązanie. Użyj tej metody, przekaż 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
Dewsworld
źródło
0

Zdecydowałem się użyć dekoratorów do rozwiązania problemu serializacji obiektów z 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ł, moje inne 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.

John Moore
źródło
0

Najbardziej podobała mi się metoda Lost Koder. Wystąpiły problemy podczas próby serializacji bardziej złożonych obiektów, których członkowie / metody nie są serializowane. 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)
Will Charlton
źródło
0

Jeśli jesteś w stanie zainstalować pakiet, polecam wypróbować dill , który zadziałał dobrze w moim projekcie. Fajną rzeczą w tym pakiecie jest to, że ma ten sam interfejs co pickle, więc jeśli już używałeś picklew swoim projekcie, możesz po prostu podstawić dilli zobaczyć, czy skrypt działa, bez zmiany kodu. Jest to więc bardzo tanie rozwiązanie do wypróbowania!

(Pełne zapobieganie ujawnieniu: nie jestem w żaden sposób związany z projektem koperku i nigdy nie brałem w nim udziału).

Zainstaluj pakiet:

pip install dill

Następnie edytuj kod do zaimportowania dillzamiast pickle:

# import pickle
import dill as pickle

Uruchom skrypt i sprawdź, czy działa. (Jeśli tak, możesz wyczyścić kod, aby nie wyświetlać picklenazwy modułu!)

Niektóre szczegóły dotyczące typów danych, które dillmogą i nie mogą serializować, na stronie projektu :

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

brak, typ, bool, int, długi, zmiennoprzecinkowy, złożony, str, Unicode, krotka, lista, dict, plik, bufor, wbudowane, zarówno stare, jak i nowe klasy stylów, instancje starych i nowych klas stylów, set, frozenset, tablica , funkcje, wyjątki

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

funkcje z wydajnością, funkcje zagnieżdżone, lambdas, komórka, metoda, niezwiązana metoda, moduł, kod, metodawrapper, dictproxy, methoddescriptor, getsetdescriptor, memberdescriptor, wrapperdescriptor, xrange, slice, notimplemented, ellipsis, quit

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

ramka, generator, traceback

thedavidmo
źródło
0

Nie widzę tu żadnej wzmianki o wersjonowaniu seryjnym ani wstecznym, więc opublikuję moje rozwiązanie, z którego korzystałem przez chwilę. Prawdopodobnie mam o wiele więcej do nauczenia się, szczególnie Java i JavaScript są prawdopodobnie bardziej dojrzałe ode mnie tutaj, ale proszę bardzo

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

Fletch F Fletch
źródło
0

Aby dodać inną opcję: Możesz użyć attrspakietu i asdictmetody.

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

json.dumps(objects, cls=ObjectEncoder)

i do konwersji

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)

klasa wygląda tak

@attr.s
class Foo(object):
    x = attr.ib()
    _obj_name = attr.ib(init=False, default='Foo')
machinekoder
źródło
0

Oprócz odpowiedzi Onura , być może chcesz poradzić sobie z typem daty i godziny , jak poniżej.
(w celu obsługi: 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)
Mark Choi
źródło
0

Najpierw musimy uczynić nasz obiekt 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
Adi Degani
źródło
0

Opierając się na Quinten Cabo „s odpowiedź :

def sterilize(obj):
    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()}
    elif hasattr(obj, '__iter__') and callable(obj.__iter__):
        return [sterilize(v) for v in obj]
    elif hasattr(obj, '__dict__'):
        return {k: sterilize(v) for k, v in obj.__dict__.items() if k not in ['__module__', '__dict__', '__weakref__', '__doc__']}
    else:
        return repr(obj)

Różnice są

  1. Działa dla każdego iterowalnego zamiast po prostu listi tuple(działa dla tablic NumPy itp.)
  2. Działa dla typów dynamicznych (zawierających __dict__ ).
  3. Obejmuje natywne typy floati Nonetak nie zamieniony na ciąg.

Zadaniem czytelnika pozostaje __slots__poradzenie sobie z klasami, które są iterowalne i mają członków, klasy, które są słownikami, a także mają członków itp.

mheyman
źródło