Cara membuat JSON kelas menjadi serial

834

Bagaimana cara membuat kelas Python serializable?

Kelas sederhana:

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

Apa yang harus saya lakukan untuk bisa mendapatkan output:

>>> import json

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

Tanpa kesalahan

Sergey
sumber
31
Sangat disayangkan bahwa semua jawaban tampaknya menjawab pertanyaan "Bagaimana cara membuat cerita bersambung kelas?" alih-alih pertanyaan tindakan "Bagaimana cara membuat kelas menjadi serial?" Jawaban-jawaban ini mengasumsikan bahwa Anda sendiri yang membuat serialisasi, alih-alih menyerahkan objek ke modul lain yang membuat serial itu.
Kyle Delaney
Jika Anda menggunakan Python3.5 +, Anda bisa menggunakan jsons. Ini akan mengubah objek Anda (dan semua atributnya secara rekursif ) menjadi dict. import jsonslihat jawaban di bawah - ini berfungsi dengan baik
tswaehn

Jawaban:

551

Apakah Anda memiliki gagasan tentang output yang diharapkan? Untuk misalnya apakah ini akan dilakukan?

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

Dalam hal ini Anda hanya dapat menelepon json.dumps(f.__dict__).

Jika Anda ingin lebih banyak output yang disesuaikan, Anda harus subclass JSONEncoderdan mengimplementasikan serialisasi kustom Anda sendiri.

Untuk contoh sepele, lihat di bawah.

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

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

Kemudian Anda meneruskan kelas ini ke json.dumps()metode sebagai clskwarg:

json.dumps(cls=MyEncoder)

Jika Anda juga ingin memecahkan kode maka Anda harus menyediakan custom object_hookke JSONDecoderkelas. Untuk misalnya

>>> 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
sumber
44
Penggunaan __dict__tidak akan berfungsi dalam semua kasus. Jika atribut belum disetel setelah objek dibuat, __dict__mungkin tidak sepenuhnya diisi. Dalam contoh di atas, Anda baik-baik saja, tetapi jika Anda memiliki atribut kelas yang juga ingin Anda enkode, itu tidak akan terdaftar __dict__kecuali mereka telah dimodifikasi dalam __init__panggilan kelas atau dengan cara lain setelah objek tersebut dipakai.
Kris Hardy
8
+1, tetapi from_json()fungsi yang digunakan sebagai objek-kait harus memiliki else: return json_objectpernyataan, sehingga dapat menangani objek umum juga.
jogojapan
8
@KrisHardy __dict__juga tidak berfungsi jika Anda menggunakan __slots__kelas gaya baru.
badp
7
Anda bisa menggunakan kustom JSONEncoderseperti di atas untuk membuat protokol kustom, seperti memeriksa keberadaan __json_serializable__metode dan memanggilnya untuk mendapatkan representasi JSON serializable objek. Ini akan menjadi sesuai dengan pola Python lainnya, seperti __getitem__, __str__, __eq__, dan __len__.
jpmc26
5
__dict__juga tidak akan bekerja secara rekursif, misalnya, jika atribut objek Anda adalah objek lain.
Neel
635

Berikut ini adalah solusi sederhana untuk fitur sederhana:

.toJSON() metode

Alih-alih kelas JSON serializable, terapkan metode serializer:

import json

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

Jadi Anda cukup menyebutnya untuk membuat serial:

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

print(me.toJSON())

akan menampilkan:

{
    "age": 35,
    "dog": {
        "name": "Apollo"
    },
    "name": "Onur"
}
Onur Yıldırım
sumber
82
Sangat terbatas. Jika Anda memiliki dict {"foo": "bar", "baz": "bat"}, itu akan membuat cerita bersambung menjadi JSON dengan mudah. Jika sebaliknya Anda memiliki {"foo": "bar", "baz": MyObject ()}, maka Anda tidak bisa. Situasi yang ideal adalah bahwa objek bersarang serial ke JSON secara rekursif, tidak secara eksplisit.
Mark E. Haase
30
Itu masih akan bekerja. Kamu hilang o.__dict___. Coba contoh Anda sendiri: 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
Apakah solusi ini dapat dibalik? Yaitu Apakah mudah untuk merekonstruksi objek dari json?
Jorge Leitao
2
@ JCLeitão No. Anda dapat memiliki dua kelas yang berbeda dengan bidang yang sama. Objek a dan b dari kelas itu (mungkin dengan properti yang sama) akan memiliki yang sama a.__dict__/ b.__dict__.
Martin Thoma
7
Ini tidak berfungsi dengan datetime.datetimeinstance. Itu melempar kesalahan berikut:'datetime.datetime' object has no attribute '__dict__'
Bruno Finger
171

Untuk kelas yang lebih kompleks, Anda dapat mempertimbangkan alat jsonpickle :

jsonpickle adalah pustaka Python untuk serialisasi dan deserialisasi objek Python kompleks ke dan dari JSON.

Pustaka Python standar untuk penyandian Python ke JSON, seperti stdlib's json, simplejson, dan demjson, hanya dapat menangani primitif Python yang memiliki setara JSON langsung (misalnya dikt, daftar, string, int, dll.). jsonpickle dibangun di atas perpustakaan ini dan memungkinkan struktur data yang lebih kompleks untuk diserialisasi ke JSON. jsonpickle sangat dapat dikonfigurasi dan dapat diperpanjang - memungkinkan pengguna untuk memilih backend JSON dan menambahkan backend tambahan.

(tautan ke jsonpickle di PyPi)

gecco
sumber
32
Berasal dari C #, inilah yang saya harapkan. Satu liner sederhana dan tidak mengacaukan kelas.
Jerther
2
jsonpickle is awesome. Itu bekerja dengan sempurna untuk objek besar, kompleks, berantakan dengan banyak tingkatan kelas
wisbucky
apakah ada contoh cara yang tepat untuk menyimpan ini ke file? Dokumentasi hanya menunjukkan cara menyandikan dan mendekode jsonpickleobjek. Juga, ini tidak dapat men-decode dicts dicts yang berisi bingkai data panda.
user5359531
3
@ user5359531 dapat Anda gunakan obj = jsonpickle.decode(file.read())dan file.write(jsonpickle.encode(obj)).
Kilian Batzner
1
Sebuah pertanyaan khusus untuk Django: apakah penggunaan jsonpickle untuk membuat serialisasi data sesi memiliki kerentanan yang sama dengan acar? (seperti dijelaskan di sini docs.djangoproject.com/en/1.11/topics/http/sessions/… )?
Paul Bormans
89

Sebagian besar jawabannya melibatkan mengubah panggilan ke json.dumps () , yang tidak selalu mungkin atau diinginkan (mungkin terjadi di dalam komponen kerangka kerja misalnya).

Jika Anda ingin dapat memanggil json.dumps (obj) apa adanya, maka solusi sederhana diwarisi dari 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

Ini berfungsi jika kelas Anda hanya representasi data dasar, untuk hal-hal rumit Anda selalu dapat mengatur kunci secara eksplisit.

andyhasit
sumber
2
Ini benar-benar bisa menjadi solusi yang bagus :) Saya percaya untuk kasus saya. Manfaat: Anda mengkomunikasikan "bentuk" objek dengan membuatnya menjadi kelas dengan init, itu inheren serializable dan terlihat dapat ditafsirkan sebagai repr .
PascalVKooten
1
Meskipun "dot-akses" masih hilang :(
PascalVKooten
2
Ahh itu sepertinya berhasil! Terima kasih, tidak yakin mengapa ini bukan jawaban yang diterima. Saya sepenuhnya setuju bahwa mengubah dumpsitu bukanlah solusi yang baik. Ngomong-ngomong, dalam kebanyakan kasus Anda mungkin ingin memiliki dictwarisan bersama dengan delegasi, yang berarti bahwa Anda akan memiliki beberapa dicttipe atribut di dalam kelas Anda, Anda kemudian akan meneruskan atribut ini sebagai parameter seperti inisialisasi super().__init__(self.elements).
cglacet
47

Saya suka jawaban Onur tetapi akan diperluas untuk memasukkan toJSON()metode opsional untuk objek untuk membuat cerita bersambung sendiri:

def dumper(obj):
    try:
        return obj.toJSON()
    except:
        return obj.__dict__
print json.dumps(some_big_object, default=dumper, indent=2)
Jason S
sumber
Saya menemukan ini sebagai keseimbangan terbaik antara menggunakan json.dumpspenanganan kustom yang ada dan memperkenalkan. Terima kasih!
Daniel Buckmaster
12
Saya sebenarnya sangat suka ini; tetapi daripada try-catchmungkin melakukan sesuatu seperti if 'toJSON' in obj.__attrs__():... untuk menghindari kegagalan diam (jika terjadi kegagalan toJSON () karena beberapa alasan selain tidak ada di sana) ... kegagalan yang berpotensi menyebabkan korupsi data.
thclark
39

Pilihan lain adalah untuk membungkus dumping JSON di kelasnya sendiri:

import json

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

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

Atau, bahkan lebih baik lagi, mensubclass kelas FileItem dari suatu JsonSerializablekelas:

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

Pengujian:

>>> f = FileItem('/foo/bar')
>>> f.toJson()
'{"fname": "/foo/bar"}'
>>> f
'{"fname": "/foo/bar"}'
>>> str(f) # string coercion
'{"fname": "/foo/bar"}'
Paulo Freitas
sumber
2
Hai, Saya tidak begitu suka pendekatan "custom encoder" ini, akan lebih baik jika Anda dapat membuat kelas Anda mudah diakses. Saya mencoba, dan mencoba dan mencoba dan tidak ada. Apakah ada ide bagaimana melakukan ini. Masalahnya adalah bahwa modul json menguji kelas Anda terhadap tipe python bawaan, dan bahkan mengatakan untuk kelas khusus buat pembuat enkode Anda :). Bisakah itu dipalsukan? Jadi saya bisa melakukan sesuatu untuk kelas saya sehingga berperilaku seperti daftar sederhana ke modul json? Saya mencoba memeriksa subclass dan instancecheck tapi tidak ada.
Bojan Radojevic
@ADRENALIN Anda bisa mewarisi dari tipe primer (mungkin dict), jika semua nilai atribut kelas serializable dan Anda tidak keberatan peretasan. Anda juga dapat menggunakan jsonpickle atau json_tricks atau sesuatu daripada yang standar (masih merupakan pembuat enkode khusus, tetapi bukan yang perlu Anda tulis atau panggil). Yang pertama mengambil contoh, yang terakhir menyimpannya sebagai atribut atribut, yang dapat Anda ubah dengan menerapkan __json__encode__/ __json_decode__(pengungkapan: Saya membuat yang terakhir).
Tandai
30

Cukup tambahkan to_jsonmetode ke kelas Anda seperti ini:

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

Dan tambahkan kode ini (dari jawaban ini ) , ke suatu tempat di atas segalanya:

from json import JSONEncoder

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

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

Ini akan me-monkey-patch modul json ketika diimpor sehingga JSONEncoder.default () secara otomatis memeriksa metode "to_json ()" khusus dan menggunakannya untuk menyandikan objek jika ditemukan.

Seperti yang dikatakan Onur, tetapi kali ini Anda tidak perlu memperbarui setiap json.dumps()proyek Anda.

John yang mewah
sumber
6
Terima kasih banyak! Ini adalah satu-satunya jawaban yang memungkinkan saya untuk melakukan apa yang saya inginkan: dapat membuat serialisasi objek tanpa mengubah kode yang ada. Metode lain kebanyakan tidak berfungsi untuk saya. Objek didefinisikan di perpustakaan pihak ketiga, dan kode serialisasi juga pihak ketiga. Mengubah mereka akan terasa canggung. Dengan metode Anda, saya hanya perlu melakukan TheObject.to_json = my_serializer.
Yongwei Wu
24

Saya menemukan masalah ini tempo hari dan menerapkan versi yang lebih umum dari Encoder untuk objek Python yang dapat menangani objek bersarang dan bidang yang diwarisi :

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

Contoh:

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)

Hasil:

{
  "a": "A", 
  "b": [
    {
      "ab": {
        "b": "B", 
        "i": "I", 
        "y": "y"
      }
    }
  ], 
  "c": {
    "c": "YES"
  }, 
  "i": "I"
}
kereta api
sumber
1
Meskipun ini agak lama..aku menghadapi beberapa kesalahan impor melingkar. Jadi alih-alih return objdi baris terakhir saya melakukan ini return super(ObjectEncoder, self).default(obj). Referensi DI SINI
SomeTypeFoo
24

Jika Anda menggunakan Python3.5 +, Anda bisa menggunakan jsons. Ini akan mengubah objek Anda (dan semua atributnya secara rekursif) menjadi dict.

import jsons

a_dict = jsons.dump(your_object)

Atau jika Anda menginginkan string:

a_str = jsons.dumps(your_object)

Atau jika kelas Anda diimplementasikan jsons.JsonSerializable:

a_dict = your_object.json
RH
sumber
3
Jika Anda dapat menggunakan Python 3.7+, saya menemukan bahwa solusi terbersih untuk mengubah kelas python menjadi dicts dan string JSON (dan sebaliknya) adalah mencampur jsonsperpustakaan dengan dataclasses . Sejauh ini, sangat baik untuk saya!
Ruluk
3
Ini adalah perpustakaan eksternal, tidak dibangun ke dalam instalasi Python standar.
Noumenon
hanya untuk kelas yang memiliki atribut slot
yehudah
Anda bisa, tetapi Anda tidak perlu menggunakan slot . Hanya ketika membuang berdasarkan tanda tangan dari kelas tertentu Anda membutuhkan slot . Dalam versi 1.1.0 yang akan datang itu juga tidak lagi terjadi.
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]')))

jika menggunakan standar json, Anda perlu mendefinisikan suatu defaultfungsi

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

print(json.dumps(User('alice', '[email protected]'), default=default))
tryer3000
sumber
2
Saya menyederhanakan ini dengan menghapus fungsi _asdict dengan lambda json.dumps(User('alice', '[email protected]'), default=lambda x: x.__dict__)
JustEngland
8

jsonterbatas dalam hal objek yang dapat dicetak, dan jsonpickle(Anda mungkin perlu pip install jsonpickle) terbatas dalam hal tidak dapat membuat teks. Jika Anda ingin memeriksa konten objek yang kelasnya tidak dapat Anda ubah, saya masih tidak bisa menemukan cara yang lebih lurus daripada:

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

Catatan: mereka tetap tidak dapat mencetak metode objek.

ribamar
sumber
6

Kelas ini dapat melakukan triknya, mengubah objek menjadi standar json.

import json


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

pemakaian:

Serializer.serialize(my_object)

bekerja di python2.7dan python3.

Koder yang Hilang
sumber
Saya paling suka metode ini. Saya mengalami masalah ketika mencoba untuk meng-serialisasi objek yang lebih kompleks yang anggota / metode tidak dapat serial. Inilah implementasi saya yang bekerja pada lebih banyak objek: `` `class Serializer (objek): @staticmethod def serialize (obj): def check (o): untuk k, v dalam o .__ dict __. Items (): coba: _ = json .dumps (v) o .__ dict __ [k] = v kecuali TypeError: o .__ dict __ [k] = str (v) mengembalikan o mengembalikan json.dumps (centang (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)
persegi panjang
sumber
Dari doc : Parameter default(obj)adalah fungsi yang harus mengembalikan versi obj yang bisa serial atau meningkatkan TypeError. Defaultnya defaulthanya memunculkan TypeError.
luckydonald
4

jaraco memberikan jawaban yang cukup rapi. Saya perlu memperbaiki beberapa hal kecil, tetapi ini berfungsi:

Kode

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

Perhatikan bahwa kita perlu dua langkah untuk memuat. Untuk saat ini, __python__properti tidak digunakan.

Seberapa umum ini?

Dengan menggunakan metode AlJohri , saya memeriksa popularitas pendekatan:

Serialisasi (Python -> JSON):

Deserialisasi (JSON -> Python):

Martin Thoma
sumber
4

Ini telah bekerja dengan baik untuk saya:

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__

lalu

class FileItem(JsonSerializable):
    ...

dan

log.debug(json.dumps(<my object>, default=JsonSerializable.dumper, indent=2))
jmhostalet
sumber
3

Jika Anda tidak keberatan menginstal paket untuk itu, Anda dapat menggunakan json-trik :

pip install json-tricks

Setelah itu Anda hanya perlu mengimpor dump(s)dari json_tricksbukan json, dan biasanya akan berfungsi:

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

yang akan memberi

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

Dan pada dasarnya itu!


Ini akan bekerja dengan baik pada umumnya. Ada beberapa pengecualian, misalnya jika terjadi hal-hal khusus __new__, atau lebih banyak keajaiban metaclass terjadi.

Jelas memuat juga berfungsi (jika tidak apa gunanya):

from json_tricks import loads
json_str = loads(json_str)

Ini mengasumsikan yang module_name.test_class.MyTestClsdapat diimpor dan tidak berubah dengan cara yang tidak kompatibel. Anda akan mendapatkan kembali sebuah instance , bukan kamus atau sejenisnya, dan itu harus merupakan salinan yang identik dengan yang Anda buang.

Jika Anda ingin mengkustomisasi bagaimana sesuatu mendapat (de) serial, Anda dapat menambahkan metode khusus ke kelas Anda, seperti:

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

yang membuat cerita bersambung hanya sebagian dari parameter atribut, sebagai contoh.

Dan sebagai bonus gratis, Anda mendapatkan (de) serialisasi array numpy, tanggal & waktu, peta yang dipesan, serta kemampuan untuk memasukkan komentar di json.

Penafian: Saya membuat json_tricks , karena saya memiliki masalah yang sama dengan Anda.

Menandai
sumber
1
Saya baru saja menguji json_tricks dan berhasil mempercantik (pada 2019).
pauljohn32
2

jsonweb tampaknya menjadi solusi terbaik untuk saya. Lihat 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
sumber
Apakah itu berfungsi dengan baik untuk objek bersarang? Termasuk decoding dan encoding
Simone Zandara
1

Ini adalah 3 sen saya ...
Ini menunjukkan serialisasi json eksplisit untuk objek python seperti pohon.
Catatan: Jika Anda benar-benar menginginkan beberapa kode seperti ini, Anda bisa menggunakan kelas FilePath bengkok .

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
sumber
1

Saya mengalami masalah ini ketika saya mencoba menyimpan model Peewee ke dalam PostgreSQL JSONField.

Setelah berjuang sebentar, inilah solusi umum.

Kunci untuk solusi saya adalah melalui kode sumber Python dan menyadari bahwa dokumentasi kode (dijelaskan di sini ) sudah menjelaskan cara memperluas yang ada json.dumpsuntuk mendukung tipe data lainnya.

Misalkan Anda saat ini memiliki model yang berisi beberapa bidang yang tidak dapat serial ke JSON dan model yang berisi bidang JSON awalnya terlihat seperti ini:

class SomeClass(Model):
    json_field = JSONField()

Cukup tentukan kustom JSONEncoderseperti ini:

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)

Dan kemudian gunakan saja JSONFieldseperti di bawah ini:

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

Kuncinya adalah default(self, obj)metode di atas. Untuk setiap satu ... is not JSON serializablekeluhan yang Anda terima dari Python, cukup tambahkan kode untuk menangani tipe JSON yang tidak dapat digunakan (seperti Enumataudatetime )

Misalnya, inilah cara saya mendukung kelas yang diturunkan dari 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)

Akhirnya, dengan kode yang diimplementasikan seperti di atas, Anda bisa mengonversi model Peewee apa saja menjadi objek JSON-seriazable seperti di bawah ini:

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

Meskipun kode di atas (agak) khusus untuk Peewee, tapi saya pikir:

  1. Ini berlaku untuk ORM lain (Django, dll) secara umum
  2. Juga, jika Anda mengerti cara json.dumpskerjanya, solusi ini juga bekerja dengan Python (sans ORM) secara umum juga

Ada pertanyaan, silakan posting di bagian komentar. Terima kasih!

sivabudh
sumber
1

Fungsi ini menggunakan rekursi untuk mengulangi setiap bagian kamus dan memanggil metode repr () dari kelas yang bukan tipe bawaan .

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
sumber
0

Saya datang dengan solusi saya sendiri. Gunakan metode ini, berikan dokumen apa saja ( dict , daftar , ObjectId dll) untuk membuat cerita bersambung.

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
sumber
0

Saya memilih untuk menggunakan dekorator untuk memecahkan masalah serialisasi objek datetime. Ini kode saya:

#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

Dengan mengimpor modul di atas, modul saya yang lain menggunakan json dengan cara normal (tanpa menentukan kata kunci default) untuk membuat serialisasi data yang berisi objek waktu tanggal. Kode serializer datetime secara otomatis dipanggil untuk json.dumps dan json.dump.

John Moore
sumber
0

Saya paling suka metode Lost Koder. Saya mengalami masalah ketika mencoba untuk meng-serialisasi objek yang lebih kompleks yang anggota / metode tidak dapat serial. Inilah implementasi saya yang bekerja pada lebih banyak objek:

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
sumber
0

Jika Anda dapat menginstal paket, saya sarankan mencoba dill , yang berfungsi dengan baik untuk proyek saya. Yang menyenangkan tentang paket ini adalah ia memiliki antarmuka yang sama pickle, jadi jika Anda sudah pernah menggunakanpickle dalam proyek Anda, Anda dapat dengan mudah menggantinya dengandill dan melihat apakah skrip berjalan, tanpa mengubah kode apa pun. Jadi ini adalah solusi yang sangat murah untuk dicoba!

(Anti-pengungkapan penuh: Saya sama sekali tidak berafiliasi dengan dan tidak pernah berkontribusi pada proyek dill.)

Instal paket:

pip install dill

Kemudian edit kode Anda untuk mengimpor dillalih-alih pickle:

# import pickle
import dill as pickle

Jalankan skrip Anda dan lihat apakah itu berfungsi. (Jika ya, Anda mungkin ingin membersihkan kode Anda sehingga Anda tidak lagi membayangipickle nama modul!)

Beberapa spesifik pada tipe data yang dilldapat dan tidak bisa membuat serial, dari halaman proyek :

dill dapat memilih jenis standar berikut:

tidak ada, ketik, bool, int, panjang, float, complex, str, unicode, tuple, list, dict, file, buffer, builtin, baik kelas gaya lama dan baru, contoh kelas gaya lama dan baru, set, frozenset, array , fungsi, pengecualian

dill juga dapat menggunakan jenis standar yang lebih 'eksotis':

fungsi dengan hasil, fungsi bersarang, lambdas, sel, metode, metode tidak terikat, modul, kode, pembungkus metode, dictproxy, deskriptor metode, getsetdescriptor, memberdescription, wrapperdescriptor, xrange, slice, notimplemented, ellipsis, berhenti

dill belum dapat memilih jenis standar ini:

bingkai, generator, traceback

thedavidmo
sumber
0

Saya melihat tidak disebutkan di sini tentang versi serial atau backcompat, jadi saya akan memposting solusi saya yang telah saya gunakan sebentar. Saya mungkin harus belajar lebih banyak, khususnya Java dan Javascript mungkin lebih matang daripada saya di sini, tetapi begini

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

Fletch F Fletch
sumber
0

Untuk menambahkan opsi lain: Anda dapat menggunakan attrspaket dan asdictmetode.

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

json.dumps(objects, cls=ObjectEncoder)

dan untuk mengkonversi kembali

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)

kelas terlihat seperti ini

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

Selain jawaban Onur , Anda mungkin ingin berurusan dengan tipe datetime seperti di bawah ini.
(untuk menangani: objek 'datetime.datetime' tidak memiliki pengecualian atribut ' dict '.)

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

Pemakaian:

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

Pertama, kita perlu membuat objek kita kompatibel dengan JSON, jadi kita bisa membuangnya menggunakan modul JSON standar. Saya melakukannya dengan cara ini:

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
sumber
0

Membangun Quinten Cabo 's jawabannya :

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)

Perbedaannya adalah

  1. Berfungsi untuk semua iterable, bukan adil listdantuple (berfungsi untuk array NumPy, dll.)
  2. Bekerja untuk tipe dinamis (yang berisi a __dict__ ).
  3. Termasuk tipe asli floatdan Nonesehingga mereka tidak dapat dikonversi ke string.

Kiri sebagai latihan untuk pembaca adalah untuk menangani __slots__, kelas-kelas yang baik iterable dan memiliki anggota, kelas-kelas yang kamus dan juga memiliki anggota, dll.

mheyman
sumber