Membuat instance instance kelas ke JSON

186

Saya mencoba untuk membuat representasi string JSON dari instance kelas dan mengalami kesulitan. Katakanlah kelas dibangun seperti ini:

class testclass:
    value1 = "a"
    value2 = "b"

Panggilan ke json.dumps dilakukan seperti ini:

t = testclass()
json.dumps(t)

Gagal dan memberi tahu saya bahwa testclass bukan JSON serializable.

TypeError: <__main__.testclass object at 0x000000000227A400> is not JSON serializable

Saya juga sudah mencoba menggunakan modul acar:

t = testclass()
print(pickle.dumps(t, pickle.HIGHEST_PROTOCOL))

Dan itu memberikan informasi instance kelas tetapi bukan konten serial instance kelas.

b'\x80\x03c__main__\ntestclass\nq\x00)\x81q\x01}q\x02b.'

Apa yang saya lakukan salah?

Feran
sumber
30
Gunakan satu baris, s = json.dumps(obj, default=lambda x: x.__dict__)untuk variabel contoh cerita bersambung objek ( self.value1, self.value2, ...). Ini cara paling sederhana dan paling lurus ke depan. Ini akan membuat cerita bersarang dari struktur objek bersarang. The defaultfungsi disebut ketika benda yang diberikan tidak langsung serializable. Anda juga dapat melihat jawaban saya di bawah ini. Saya menemukan jawaban populer tidak perlu rumit, yang mungkin benar sejak lama.
codeman48
1
Anda testclasstidak memiliki __init__()metode, jadi semua instance akan berbagi dua atribut kelas yang sama ( value1dan value2) yang didefinisikan dalam pernyataan kelas. Apakah Anda memahami perbedaan antara kelas dan contoh dari satu kelas?
martineau
1
Ada pustaka python untuk github.com/jsonpickle/jsonpickle ini (berkomentar karena jawabannya terlalu di bawah di utas dan tidak dapat dijangkau.)
semoga

Jawaban:

238

Masalah dasarnya adalah bahwa enkoder JSON json.dumps()hanya tahu cara membuat serial suatu rangkaian terbatas jenis objek secara default, semua tipe bawaan. Daftar di sini: https://docs.python.org/3.3/library/json.html#encoders-and-decoders

Salah satu solusi yang baik adalah membuat kelas Anda mewarisi dari JSONEncoderdan kemudian mengimplementasikan JSONEncoder.default()fungsi tersebut, dan membuat fungsi itu memancarkan JSON yang benar untuk kelas Anda.

Sebuah solusi sederhana akan memanggil json.dumps()pada .__dict__anggota contoh itu. Itu adalah standar Python dictdan jika kelas Anda sederhana itu akan menjadi JSON serializable.

class Foo(object):
    def __init__(self):
        self.x = 1
        self.y = 2

foo = Foo()
s = json.dumps(foo) # raises TypeError with "is not JSON serializable"

s = json.dumps(foo.__dict__) # s set to: {"x":1, "y":2}

Pendekatan di atas dibahas dalam posting blog ini:

    Menerialisasikan objek Python sewenang-wenang ke JSON menggunakan __dict__

steveha
sumber
3
Saya mencoba ini. Hasil akhir dari panggilan ke json.dumps (t .__ dict__) hanya {}.
ferhan
6
Itu karena kelas Anda tidak memiliki .__init__()fungsi metode, jadi instance kelas Anda memiliki kamus kosong. Dengan kata lain, {}adalah hasil yang benar untuk kode contoh Anda.
steveha
3
Terima kasih. Ini caranya. Saya menambahkan init sederhana tanpa parameter dan sekarang memanggil json.dumps (t .__ dict__) mengembalikan data yang tepat dalam format: {"value2": "345", "value1": "123"} Saya telah melihat posting seperti ini sebelumnya, tidak yakin apakah saya memerlukan serializer khusus untuk anggota, membutuhkan init tidak disebutkan secara eksplisit atau saya melewatkannya. Terima kasih.
ferhan
3
Ini bekerja untuk satu kelas tetapi tidak dengan objek terkait kelas
Nwawel A Iroume
2
@NwawelAIroume: Benar. Jika Anda memiliki objek yang mis. is not JSON serializable
Mengandung
57

Ada satu cara yang bagus untuk saya yang bisa Anda coba:

json.dumps()dapat mengambil default parameter opsional tempat Anda dapat menentukan fungsi serializer khusus untuk jenis yang tidak dikenal, yang dalam kasus saya terlihat seperti

def serialize(obj):
    """JSON serializer for objects not serializable by default json code"""

    if isinstance(obj, date):
        serial = obj.isoformat()
        return serial

    if isinstance(obj, time):
        serial = obj.isoformat()
        return serial

    return obj.__dict__

Dua if pertama adalah untuk serialisasi tanggal dan waktu dan kemudian ada pengembalian obj.__dict__untuk objek lain.

panggilan terakhir terlihat seperti:

json.dumps(myObj, default=serialize)

Ini sangat baik ketika Anda membuat serial koleksi dan Anda tidak ingin menelepon __dict__ secara eksplisit untuk setiap objek. Ini dilakukan untuk Anda secara otomatis.

Sejauh ini bekerja sangat baik untuk saya, menantikan pikiran Anda.

Brokoli
sumber
Saya mengerti NameError: name 'serialize' is not defined. Ada tips?
Kyle Delaney
Sangat bagus. Hanya untuk kelas yang memiliki slot:try: dict = obj.__dict__ except AttributeError: dict = {s: getattr(obj, s) for s in obj.__slots__ if hasattr(obj, s)} return dict
fantastory
Sungguh menakjubkan bahwa bahasa yang begitu populer tidak memiliki satu baris pun untuk menyamai objek. Pasti karena tidak diketik secara statis.
TheRennen
49

Anda dapat menentukan defaultparameter bernama dalam json.dumps()fungsi:

json.dumps(obj, default=lambda x: x.__dict__)

Penjelasan:

Bentuk dokumen ( 2.7 , 3.6 ):

``default(obj)`` is a function that should return a serializable version
of obj or raise TypeError. The default simply raises TypeError.

(Bekerja pada Python 2.7 dan Python 3.x)

Catatan: Dalam hal ini Anda perlu instancevariabel dan bukan classvariabel, seperti contoh dalam pertanyaan yang coba dilakukan. (Saya mengasumsikan si penanya dimaksudkan class instancesebagai objek kelas)

Saya belajar ini dulu dari jawaban @ phihag di sini . Menemukannya sebagai cara paling sederhana dan terbersih untuk melakukan pekerjaan itu.

codeman48
sumber
6
Ini bekerja untuk saya, tetapi karena anggota datetime.date saya mengubahnya sedikit:default=lambda x: getattr(x, '__dict__', str(x))
Dakota Hawkins
@ Dakota bekerja dengan baik; datetime.dateadalah implementasi C karena itu tidak memiliki __dict__atribut. IMHO demi keseragaman, datetime.dateharus memilikinya ...
codeman48
22

Saya hanya melakukan:

data=json.dumps(myobject.__dict__)

Ini bukan jawaban lengkap, dan jika Anda memiliki semacam kelas objek yang rumit, Anda tentu tidak akan mendapatkan semuanya. Namun saya menggunakan ini untuk beberapa objek sederhana saya.

Salah satu yang berfungsi dengan sangat baik adalah kelas "opsi" yang Anda dapatkan dari modul OptionParser. Ini dia bersama dengan permintaan JSON itu sendiri.

  def executeJson(self, url, options):
        data=json.dumps(options.__dict__)
        if options.verbose:
            print data
        headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
        return requests.post(url, data, headers=headers)
SpiRail
sumber
Anda mungkin ingin menghapus sendiri, jika Anda tidak menggunakan ini di dalam kelas.
SpiRail
3
Itu akan bekerja dengan baik, selama objek tersebut tidak terdiri dari objek lain.
Haroldo_OK
19

Menggunakan jsonpickle

import jsonpickle

object = YourClass()
json_object = jsonpickle.encode(object)
gies0r
sumber
5

JSON tidak benar-benar dimaksudkan untuk membuat serial objek Python sewenang-wenang. Ini bagus untuk serialisasi dictobjek, tetapi picklemodul ini benar-benar apa yang seharusnya Anda gunakan secara umum. Keluaran dari pickletidak benar-benar dapat dibaca manusia, tetapi seharusnya membongkar dengan baik. Jika Anda bersikeras menggunakan JSON, Anda bisa memeriksa jsonpicklemodulnya, yang merupakan pendekatan hybrid yang menarik.

https://github.com/jsonpickle/jsonpickle

Brendan Wood
sumber
9
Masalah utama yang saya lihat dengan acar adalah bahwa itu format khusus Python, sedangkan JSON adalah format platform-independen. JSON sangat berguna jika Anda menulis aplikasi web atau backend untuk beberapa aplikasi seluler. Itu telah dikatakan, terima kasih untuk menunjukkan jsonpickle.
Haroldo_OK
@ Haroldo_OK Bukankah jsonpickle masih mengekspor ke JSON, hanya saja tidak bisa dibaca oleh manusia?
Caelum
4

Berikut adalah dua fungsi sederhana untuk serialisasi kelas non-canggih, tidak ada yang mewah seperti yang dijelaskan sebelumnya.

Saya menggunakan ini untuk hal-hal tipe konfigurasi karena saya dapat menambahkan anggota baru ke kelas tanpa penyesuaian kode.

import json

class SimpleClass:
    def __init__(self, a=None, b=None, c=None):
        self.a = a
        self.b = b
        self.c = c

def serialize_json(instance=None, path=None):
    dt = {}
    dt.update(vars(instance))

    with open(path, "w") as file:
        json.dump(dt, file)

def deserialize_json(cls=None, path=None):
    def read_json(_path):
        with open(_path, "r") as file:
            return json.load(file)

    data = read_json(path)

    instance = object.__new__(cls)

    for key, value in data.items():
        setattr(instance, key, value)

    return instance

# Usage: Create class and serialize under Windows file system.
write_settings = SimpleClass(a=1, b=2, c=3)
serialize_json(write_settings, r"c:\temp\test.json")

# Read back and rehydrate.
read_settings = deserialize_json(SimpleClass, r"c:\temp\test.json")

# results are the same.
print(vars(write_settings))
print(vars(read_settings))

# output:
# {'c': 3, 'b': 2, 'a': 1}
# {'c': 3, 'b': 2, 'a': 1}
GBGOLC
sumber
3

Ada beberapa jawaban yang baik tentang bagaimana memulai melakukan ini. Tetapi ada beberapa hal yang perlu diingat:

  • Bagaimana jika instance bersarang di dalam struktur data besar?
  • Bagaimana jika juga ingin nama kelasnya?
  • Bagaimana jika Anda ingin membatalkan deserialisasi instance?
  • Bagaimana jika Anda menggunakan __slots__bukan __dict__?
  • Bagaimana jika Anda tidak ingin melakukannya sendiri?

json-trick adalah perpustakaan (yang saya buat dan kontribusi lainnya) yang telah dapat melakukan ini cukup lama. Sebagai contoh:

class MyTestCls:
    def __init__(self, **kwargs):
        for k, v in kwargs.items():
            setattr(self, k, v)

cls_instance = MyTestCls(s='ub', dct={'7': 7})

json = dumps(cls_instance, indent=4)
instance = loads(json)

Anda akan mendapatkan instance Anda kembali. Di sini json terlihat seperti ini:

{
    "__instance_type__": [
        "json_tricks.test_class",
        "MyTestCls"
    ],
    "attributes": {
        "s": "ub",
        "dct": {
            "7": 7
        }
    }
}

Jika Anda ingin membuat solusi sendiri, Anda mungkin melihat sumbernya json-tricksagar tidak melupakan beberapa kasus khusus (seperti __slots__).

Ia juga melakukan tipe lain seperti array numpy, datetimes, bilangan kompleks; itu juga memungkinkan untuk komentar.

Menandai
sumber
3

Python3.x

Pendekatan terbaik yang bisa saya capai dengan pengetahuan saya adalah ini.
Perhatikan bahwa kode ini memperlakukan set () juga.
Pendekatan ini generik hanya membutuhkan ekstensi kelas (dalam contoh kedua).
Perhatikan bahwa saya hanya melakukannya ke file, tetapi mudah untuk mengubah perilaku sesuai selera Anda.

Namun ini adalah CoDec.

Dengan sedikit lebih banyak pekerjaan, Anda dapat membangun kelas Anda dengan cara lain. Saya menganggap konstruktor default untuk instance itu, lalu saya memperbarui dict kelas.

import json
import collections


class JsonClassSerializable(json.JSONEncoder):

    REGISTERED_CLASS = {}

    def register(ctype):
        JsonClassSerializable.REGISTERED_CLASS[ctype.__name__] = ctype

    def default(self, obj):
        if isinstance(obj, collections.Set):
            return dict(_set_object=list(obj))
        if isinstance(obj, JsonClassSerializable):
            jclass = {}
            jclass["name"] = type(obj).__name__
            jclass["dict"] = obj.__dict__
            return dict(_class_object=jclass)
        else:
            return json.JSONEncoder.default(self, obj)

    def json_to_class(self, dct):
        if '_set_object' in dct:
            return set(dct['_set_object'])
        elif '_class_object' in dct:
            cclass = dct['_class_object']
            cclass_name = cclass["name"]
            if cclass_name not in self.REGISTERED_CLASS:
                raise RuntimeError(
                    "Class {} not registered in JSON Parser"
                    .format(cclass["name"])
                )
            instance = self.REGISTERED_CLASS[cclass_name]()
            instance.__dict__ = cclass["dict"]
            return instance
        return dct

    def encode_(self, file):
        with open(file, 'w') as outfile:
            json.dump(
                self.__dict__, outfile,
                cls=JsonClassSerializable,
                indent=4,
                sort_keys=True
            )

    def decode_(self, file):
        try:
            with open(file, 'r') as infile:
                self.__dict__ = json.load(
                    infile,
                    object_hook=self.json_to_class
                )
        except FileNotFoundError:
            print("Persistence load failed "
                  "'{}' do not exists".format(file)
                  )


class C(JsonClassSerializable):

    def __init__(self):
        self.mill = "s"


JsonClassSerializable.register(C)


class B(JsonClassSerializable):

    def __init__(self):
        self.a = 1230
        self.c = C()


JsonClassSerializable.register(B)


class A(JsonClassSerializable):

    def __init__(self):
        self.a = 1
        self.b = {1, 2}
        self.c = B()

JsonClassSerializable.register(A)

A().encode_("test")
b = A()
b.decode_("test")
print(b.a)
print(b.b)
print(b.c.a)

Edit

Dengan beberapa penelitian saya menemukan cara untuk menggeneralisasi tanpa perlu panggilan metode register SUPERCLASS , menggunakan metaclass

import json
import collections

REGISTERED_CLASS = {}

class MetaSerializable(type):

    def __call__(cls, *args, **kwargs):
        if cls.__name__ not in REGISTERED_CLASS:
            REGISTERED_CLASS[cls.__name__] = cls
        return super(MetaSerializable, cls).__call__(*args, **kwargs)


class JsonClassSerializable(json.JSONEncoder, metaclass=MetaSerializable):

    def default(self, obj):
        if isinstance(obj, collections.Set):
            return dict(_set_object=list(obj))
        if isinstance(obj, JsonClassSerializable):
            jclass = {}
            jclass["name"] = type(obj).__name__
            jclass["dict"] = obj.__dict__
            return dict(_class_object=jclass)
        else:
            return json.JSONEncoder.default(self, obj)

    def json_to_class(self, dct):
        if '_set_object' in dct:
            return set(dct['_set_object'])
        elif '_class_object' in dct:
            cclass = dct['_class_object']
            cclass_name = cclass["name"]
            if cclass_name not in REGISTERED_CLASS:
                raise RuntimeError(
                    "Class {} not registered in JSON Parser"
                    .format(cclass["name"])
                )
            instance = REGISTERED_CLASS[cclass_name]()
            instance.__dict__ = cclass["dict"]
            return instance
        return dct

    def encode_(self, file):
        with open(file, 'w') as outfile:
            json.dump(
                self.__dict__, outfile,
                cls=JsonClassSerializable,
                indent=4,
                sort_keys=True
            )

    def decode_(self, file):
        try:
            with open(file, 'r') as infile:
                self.__dict__ = json.load(
                    infile,
                    object_hook=self.json_to_class
                )
        except FileNotFoundError:
            print("Persistence load failed "
                  "'{}' do not exists".format(file)
                  )


class C(JsonClassSerializable):

    def __init__(self):
        self.mill = "s"


class B(JsonClassSerializable):

    def __init__(self):
        self.a = 1230
        self.c = C()


class A(JsonClassSerializable):

    def __init__(self):
        self.a = 1
        self.b = {1, 2}
        self.c = B()


A().encode_("test")
b = A()
b.decode_("test")
print(b.a)
# 1
print(b.b)
# {1, 2}
print(b.c.a)
# 1230
print(b.c.c.mill)
# s
Davi Abreu Wasserberg
sumber
2

Saya percaya daripada pewarisan seperti yang disarankan dalam jawaban yang diterima, lebih baik menggunakan polimorfisme. Kalau tidak, Anda harus memiliki pernyataan if if big untuk menyesuaikan pengkodean setiap objek. Itu berarti membuat pembuat enkode default generik untuk JSON sebagai:

def jsonDefEncoder(obj):
   if hasattr(obj, 'jsonEnc'):
      return obj.jsonEnc()
   else: #some default behavior
      return obj.__dict__

dan kemudian memiliki jsonEnc()fungsi di setiap kelas yang ingin Anda serialkan. misalnya

class A(object):
   def __init__(self,lengthInFeet):
      self.lengthInFeet=lengthInFeet
   def jsonEnc(self):
      return {'lengthInMeters': lengthInFeet * 0.3 } # each foot is 0.3 meter

Lalu kamu menelepon json.dumps(classInstance,default=jsonDefEncoder)

hat
sumber