Cara mengonversi data JSON menjadi objek Python

281

Saya ingin menggunakan Python untuk mengubah data JSON menjadi objek Python.

Saya menerima objek data JSON dari Facebook API, yang ingin saya simpan di basis data saya.

Tampilan saya saat ini di Django (Python) ( request.POSTberisi JSON):

response = request.POST
user = FbApiUser(user_id = response['id'])
user.name = response['name']
user.username = response['username']
user.save()
  • Ini berfungsi dengan baik, tetapi bagaimana saya menangani objek data JSON yang kompleks?

  • Bukankah lebih baik jika saya bisa mengubah objek JSON ini menjadi objek Python agar mudah digunakan?

Sai Krishna
sumber
Biasanya JSON dikonversi ke daftar vanilla atau dicts. Itukah yang kamu inginkan? Atau apakah Anda berharap untuk mengkonversi JSON langsung ke tipe kustom?
Shakakai
Saya ingin mengubahnya menjadi objek, sesuatu yang dapat saya akses menggunakan "." . Seperti dari contoh di atas -> reponse.name, response.education.id dll ....
Sai Krishna
44
Menggunakan dicts adalah cara yang lemah untuk melakukan pemrograman berorientasi objek. Kamus adalah cara yang sangat buruk untuk mengomunikasikan harapan kepada pembaca kode Anda. Menggunakan kamus, bagaimana Anda dapat dengan jelas dan dapat digunakan kembali menentukan bahwa beberapa pasangan nilai kunci kamus diperlukan, sementara yang lain tidak? Bagaimana dengan mengonfirmasi bahwa nilai yang diberikan berada dalam kisaran atau set yang dapat diterima? Bagaimana dengan fungsi yang khusus untuk jenis objek yang Anda kerjakan (metode alias)? Kamus sangat berguna dan serbaguna, tetapi terlalu banyak dev bertindak seperti mereka lupa Python adalah bahasa berorientasi objek karena suatu alasan.
Stew
1
Ada pustaka python untuk github.com/jsonpickle/jsonpickle ini (berkomentar karena jawabannya terlalu di bawah di utas dan tidak dapat dijangkau.)
semoga

Jawaban:

356

Anda dapat melakukannya dalam satu baris, menggunakan namedtupledan object_hook:

import json
from collections import namedtuple

data = '{"name": "John Smith", "hometown": {"name": "New York", "id": 123}}'

# Parse JSON into an object with attributes corresponding to dict keys.
x = json.loads(data, object_hook=lambda d: namedtuple('X', d.keys())(*d.values()))
print x.name, x.hometown.name, x.hometown.id

atau, untuk menggunakan kembali ini dengan mudah:

def _json_object_hook(d): return namedtuple('X', d.keys())(*d.values())
def json2obj(data): return json.loads(data, object_hook=_json_object_hook)

x = json2obj(data)

Jika Anda ingin menangani kunci yang tidak nama-nama atribut yang baik, check out namedtuple's renameparameter .

DS.
sumber
8
ini dapat mengakibatkan kesalahan Nilai, ValueError: Ketikkan nama dan nama bidang tidak dapat dimulai dengan angka: '123'
PvdL
3
Sebagai pemula untuk Python, saya tertarik jika ini adalah hal yang aman juga ketika keamanan menjadi masalah.
benjist
8
Ini menciptakan kelas baru yang berbeda setiap kali bertemu objek JSON saat parsing, kan?
fikr4n
2
Menarik. Saya pikir mengandalkan d.keys()dan d.values()mengulangi dalam urutan yang sama tidak dijamin, tetapi saya salah. The docs mengatakan: "Jika kunci, nilai-nilai dan item pandangan yang iterasi berakhir tanpa modifikasi intervensi ke dalam kamus, urutan item akan langsung bersesuaian.". Baik untuk mengetahui blok kode lokal kecil seperti itu. Saya akan menambahkan komentar meskipun untuk secara eksplisit mengingatkan pengelola kode ketergantungan seperti itu.
cfi
1
Saya tidak mengetahui adanya operasi membalikkan tujuan umum yang bagus. Setiap individu yang diberi nama dapat diubah menjadi dikt menggunakan x._asdict(), yang mungkin membantu untuk kasus-kasus sederhana.
DS.
127

Lihat bagian berjudul Mengkhususkan decoding objek JSON dalam json dokumentasi modul . Anda bisa menggunakannya untuk mendekode objek JSON menjadi tipe Python tertentu.

Ini sebuah contoh:

class User(object):
    def __init__(self, name, username):
        self.name = name
        self.username = username

import json
def object_decoder(obj):
    if '__type__' in obj and obj['__type__'] == 'User':
        return User(obj['name'], obj['username'])
    return obj

json.loads('{"__type__": "User", "name": "John Smith", "username": "jsmith"}',
           object_hook=object_decoder)

print type(User)  # -> <type 'type'>

Memperbarui

Jika Anda ingin mengakses data dalam kamus melalui modul json, lakukan ini:

user = json.loads('{"__type__": "User", "name": "John Smith", "username": "jsmith"}')
print user['name']
print user['username']

Sama seperti kamus biasa.

Shakakai
sumber
1
Hai, saya baru saja membaca dan saya menyadari bahwa kamus akan sepenuhnya berfungsi, hanya saja saya bertanya-tanya bagaimana cara mengubah objek JSON menjadi kamus dan bagaimana cara mengakses data ini dari kamus?
Sai Krishna
Luar biasa, hampir jelas, hanya ingin tahu satu hal kecil lagi bahwa jika ada objek ini -> {'pendidikan': {'name1': 456, 'name2': 567}}, bagaimana cara mengakses data ini?
Sai Krishna
itu hanya akan menjadi topLevelData ['pendidikan'] ['name1'] ==> 456. masuk akal?
Shakakai
1
@ Ben: Saya pikir komentar Anda tidak pantas. Dari semua jawaban di sini saat ini, itu adalah satu-satunya yang mendapatkan kelas dengan benar. Yang berarti: Ini adalah operasi sekali jalan dan hasilnya menggunakan jenis yang benar. Pickle sendiri untuk aplikasi yang berbeda dari JSON (rep biner versus tekstual) dan jsonpickle adalah lib yang tidak standar. Saya akan tertarik untuk melihat bagaimana Anda memecahkan masalah bahwa std json lib tidak menyediakan pohon parse atas ke objek kait
cfi
Saya harus setuju dengan @Ben tentang ini. Ini solusi yang sangat buruk. Tidak scalable sama sekali. Anda harus mempertahankan nama bidang sebagai string dan sebagai bidang. Jika Anda ingin refactor bidang Anda decoding akan gagal (tentu saja data serial yang sudah tidak lagi relevan). Konsep yang sama sudah diterapkan dengan baik dengan jsonpickle
guyarad
99

Ini bukan kode golf, tapi di sini adalah trik terpendek saya, menggunakan types.SimpleNamespacesebagai wadah untuk objek JSON.

Dibandingkan dengan namedtuplesolusi terkemuka , itu adalah:

  • mungkin lebih cepat / lebih kecil karena tidak membuat kelas untuk setiap objek
  • singkat
  • tidak ada renameopsi, dan mungkin batasan yang sama pada kunci yang bukan pengidentifikasi yang valid (digunakan di setattrbawah penutup)

Contoh:

from __future__ import print_function
import json

try:
    from types import SimpleNamespace as Namespace
except ImportError:
    # Python 2.x fallback
    from argparse import Namespace

data = '{"name": "John Smith", "hometown": {"name": "New York", "id": 123}}'

x = json.loads(data, object_hook=lambda d: Namespace(**d))

print (x.name, x.hometown.name, x.hometown.id)
eddygeek
sumber
2
Omong-omong, perpustakaan serialisasi Marshmallow menawarkan fitur serupa dengan @post_loaddekoratornya. marshmallow.readthedocs.io/en/latest/…
Taylor Edmiston
3
Untuk menghindari ketergantungan pada argparse: ganti impor argparse dengan from types import SimpleNamespacedan gunakan:x = json.loads(data, object_hook=lambda d: SimpleNamespace(**d))
maxschlepzig
8
Ini adalah solusi paling elegan, harus di atas.
ScalaWilliam
4
Diedit untuk menggunakan solusi @ maxschlepzig ketika berjalan di bawah Python 3.x ( types.SimpleNamespacesayangnya, tidak ada di 2.7, sayangnya).
Dan Lenski
1
mengapa print_function?
chwi
90

Anda bisa mencoba ini:

class User(object):
    def __init__(self, name, username, *args, **kwargs):
        self.name = name
        self.username = username

import json
j = json.loads(your_json)
u = User(**j)

Cukup buat Obyek baru, dan berikan parameter sebagai peta.

cmaluenda
sumber
1
Saya mendapatkan TypeError: Objek 'Pengguna' tidak dapat disubkripsikan
Mahdi
1
Ini harus menjadi jawaban yang diterima. bekerja untuk saya iklan jauh lebih sederhana daripada yang lain.
Izik
Saya tidak menggunakan * args, ** kwargs, tetapi solusinya berhasil.
Malkaviano
1
Pengguna (** j) mengatakan tidak ada parameter nama dan nama pengguna, juga bagaimana dikte diinisialisasi?
Aaron Stainback
40

Inilah alternatif acar json yang cepat dan kotor

import json

class User:
    def __init__(self, name, username):
        self.name = name
        self.username = username

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

    @classmethod
    def from_json(cls, json_str):
        json_dict = json.loads(json_str)
        return cls(**json_dict)

# example usage
User("tbrown", "Tom Brown").to_json()
User.from_json(User("tbrown", "Tom Brown").to_json()).to_json()
ubershmekel
sumber
1
Ini bukan pendekatan yang baik. Pada awalnya to_json dan from_json tidak boleh ditempatkan di kelas Anda. Pada detik itu tidak akan berfungsi untuk kelas bersarang.
Jurass
17

Untuk objek yang kompleks, Anda dapat menggunakan JSON Pickle

Pustaka Python untuk membuat serialisasi setiap objek grafik yang berubah-ubah menjadi JSON. Ini dapat mengambil hampir semua objek Python dan mengubah objek menjadi JSON. Selain itu, ia dapat menyusun kembali objek kembali menjadi Python.

sputnikus
sumber
6
Saya pikir jsonstruct lebih baik. jsonstruct originally a fork of jsonpickle (Thanks guys!). The key difference between this library and jsonpickle is that during deserialization, jsonpickle requires Python types to be recorded as part of the JSON. This library intends to remove this requirement, instead, requires a class to be passed in as an argument so that its definition can be inspected. It will then return an instance of the given class. This approach is similar to how Jackson (of Java) works.
Abhishek Gupta
3
Masalah dengan jsonstruct adalah bahwa itu tampaknya tidak dipertahankan (pada kenyataannya, itu tampak ditinggalkan) dan gagal untuk mengkonversi daftar objek, seperti '[{"name":"object1"},{"name":"object2"}]'. jsonpickle juga tidak menanganinya dengan baik.
LS
1
Saya tidak tahu mengapa jawaban ini tidak mendapatkan lebih banyak suara. Sebagian besar solusi lain cukup di luar sana. Seseorang mengembangkan perpustakaan yang bagus untuk de / serialisasi JSON - mengapa tidak menggunakannya? Selain itu, tampaknya berfungsi dengan baik dengan daftar - apa masalah Anda dengannya @LS?
guyarad
1
@guyarad, masalahnya adalah: x = jsonpickle.decode ('[{"name": "object1"}, {"name": "object2"}]') memberikan daftar kamus ([{'name': ' object1 '}, {' name ':' object2 '}]), bukan daftar objek dengan properti (x [0] .name ==' object1 '), yang merupakan pertanyaan awal yang diperlukan. Untuk mendapatkan itu, saya akhirnya menggunakan pendekatan object_hook / Namespace yang disarankan oleh eddygeek, tetapi pendekatan cepat / kotor oleh ubershmekel terlihat bagus juga. Saya pikir saya bisa menggunakan object_hook dengan set_encoder_options jsonpickle () (tidak berdokumen!), Tetapi akan membutuhkan lebih banyak kode daripada modul json dasar. Saya ingin terbukti salah!
LS
@ LS jika Anda tidak memiliki kontrol atas input, yang benar-benar apa yang diminta OP, jsonpickle tidak ideal karena mengharapkan tipe aktual di setiap level (dan akan menganggap tipe dasar jika hilang). Kedua solusi itu "imut".
guyarad
12

Jika Anda menggunakan Python 3.5+, Anda bisa menggunakan jsonsuntuk membuat serial dan deserialize ke objek Python lama:

import jsons

response = request.POST

# You'll need your class attributes to match your dict keys, so in your case do:
response['id'] = response.pop('user_id')

# Then you can load that dict into your class:
user = jsons.load(response, FbApiUser)

user.save()

Anda juga bisa membuat FbApiUserwarisan dari jsons.JsonSerializableuntuk lebih elegan:

user = FbApiUser.from_json(response)

Contoh-contoh ini akan berfungsi jika kelas Anda terdiri dari tipe standar Python, seperti string, integer, daftar, datetimes, dll. jsonsLib akan memerlukan petunjuk tipe untuk tipe kustom.

RH
sumber
7

Jika Anda menggunakan python 3.6+, Anda dapat menggunakan marshmallow-dataclass . Berlawanan dengan semua solusi yang tercantum di atas, ini sederhana, dan mengetik aman:

from marshmallow_dataclass import dataclass

@dataclass
class User:
    name: str

user, err = User.Schema().load({"name": "Ramirez"})
lovasoa
sumber
TypeError: make_data_class() got an unexpected keyword argument 'many'
JOhn
@JOhn: Anda harus membuka masalah dengan test case yang dapat direproduksi di github.com/lovasoa/marshmallow_dataclass/issues
lovasoa
6

Meningkatkan jawaban lovasoa yang sangat bagus.

Jika Anda menggunakan python 3.6+, Anda dapat menggunakan:
pip install marshmallow-enumdan
pip install marshmallow-dataclass

Sederhana dan aman.

Anda bisa mengubah kelas Anda dalam string-json dan sebaliknya:

Dari Object ke String Json:

    from marshmallow_dataclass import dataclass
    user = User("Danilo","50","RedBull",15,OrderStatus.CREATED)
    user_json = User.Schema().dumps(user)
    user_json_str = user_json.data

Dari String Json ke Object:

    json_str = '{"name":"Danilo", "orderId":"50", "productName":"RedBull", "quantity":15, "status":"Created"}'
    user, err = User.Schema().loads(json_str)
    print(user,flush=True)

Definisi kelas:

class OrderStatus(Enum):
    CREATED = 'Created'
    PENDING = 'Pending'
    CONFIRMED = 'Confirmed'
    FAILED = 'Failed'

@dataclass
class User:
    def __init__(self, name, orderId, productName, quantity, status):
        self.name = name
        self.orderId = orderId
        self.productName = productName
        self.quantity = quantity
        self.status = status

    name: str
    orderId: str
    productName: str
    quantity: int
    status: OrderStatus
danilo
sumber
1
Anda tidak perlu konstruktor, cukup kirimkan init = Benar untuk dataclass dan Anda baik untuk pergi.
Josef Korbel
5

Saya telah menulis kerangka serialisasi kecil (de) yang disebut any2any yang membantu melakukan transformasi kompleks antara dua jenis Python.

Dalam kasus Anda, saya kira Anda ingin mengubah dari kamus (diperoleh dengan json.loads) menjadi objek yang kompleks response.education ; response.name, dengan struktur bersarang response.education.id, dll ... Jadi itulah kerangka yang dibuat untuk ini. Dokumentasi belum bagus, tetapi dengan menggunakan any2any.simple.MappingToObject, Anda harus dapat melakukannya dengan sangat mudah. Silahkan bertanya jika Anda memerlukan bantuan.

sebpiq
sumber
Sebpiq, telah menginstal any2any dan saya kesulitan memahami urutan panggilan metode yang dimaksud. Bisakah Anda memberikan contoh sederhana dari mengonversi kamus ke objek Python dengan properti untuk setiap tombol?
sansjoe
Hai @sansjoe! Jika Anda telah menginstalnya dari pypi, versi ini benar-benar ketinggalan zaman, saya telah membuat refactoring lengkap beberapa minggu yang lalu. Anda harus menggunakan versi github (saya harus membuat rilis yang tepat!)
sebpiq
Saya menginstalnya dari pypy karena github mengatakan untuk menginstalnya dari pypy. Juga, Anda mengatakan pypy sudah ketinggalan zaman beberapa bulan yang lalu .. Itu tidak berhasil :( Saya mengajukan laporan bug ke sana! Github.com/sebpiq/any2any/issues/11
sneilan
4

Karena tidak ada yang memberikan jawaban seperti milik saya, saya akan mempostingnya di sini.

Ini adalah kelas yang kuat yang dapat dengan mudah mengkonversi bolak-balik antara json strdan dictyang saya salin dari jawaban saya untuk pertanyaan lain :

import json

class PyJSON(object):
    def __init__(self, d):
        if type(d) is str:
            d = json.loads(d)

        self.from_dict(d)

    def from_dict(self, d):
        self.__dict__ = {}
        for key, value in d.items():
            if type(value) is dict:
                value = PyJSON(value)
            self.__dict__[key] = value

    def to_dict(self):
        d = {}
        for key, value in self.__dict__.items():
            if type(value) is PyJSON:
                value = value.to_dict()
            d[key] = value
        return d

    def __repr__(self):
        return str(self.to_dict())

    def __setitem__(self, key, value):
        self.__dict__[key] = value

    def __getitem__(self, key):
        return self.__dict__[key]

json_str = """... json string ..."""

py_json = PyJSON(json_str)
Božo Stojković
sumber
2

Mengubah respons @DS sedikit, untuk memuat dari file:

def _json_object_hook(d): return namedtuple('X', d.keys())(*d.values())
def load_data(file_name):
  with open(file_name, 'r') as file_data:
    return file_data.read().replace('\n', '')
def json2obj(file_name): return json.loads(load_data(file_name), object_hook=_json_object_hook)

Satu hal: ini tidak dapat memuat item dengan angka di depan. Seperti ini:

{
  "1_first_item": {
    "A": "1",
    "B": "2"
  }
}

Karena "1_first_item" bukan nama bidang python yang valid.

Valtoni Boaventura
sumber
2

Saat mencari solusi, saya menemukan posting blog ini: https://blog.mosthege.net/2016/11/12/json-deserialization-of-nested-objects/

Ini menggunakan teknik yang sama seperti yang dinyatakan dalam jawaban sebelumnya tetapi dengan penggunaan dekorator. Hal lain yang saya temukan berguna adalah kenyataan bahwa ia mengembalikan objek yang diketik pada akhir deserialisation

class JsonConvert(object):
    class_mappings = {}

    @classmethod
    def class_mapper(cls, d):
        for keys, cls in clsself.mappings.items():
            if keys.issuperset(d.keys()):   # are all required arguments present?
                return cls(**d)
        else:
            # Raise exception instead of silently returning None
            raise ValueError('Unable to find a matching class for object: {!s}'.format(d))

    @classmethod
    def complex_handler(cls, Obj):
        if hasattr(Obj, '__dict__'):
            return Obj.__dict__
        else:
            raise TypeError('Object of type %s with value of %s is not JSON serializable' % (type(Obj), repr(Obj)))

    @classmethod
    def register(cls, claz):
        clsself.mappings[frozenset(tuple([attr for attr,val in cls().__dict__.items()]))] = cls
        return cls

    @classmethod
    def to_json(cls, obj):
        return json.dumps(obj.__dict__, default=cls.complex_handler, indent=4)

    @classmethod
    def from_json(cls, json_str):
        return json.loads(json_str, object_hook=cls.class_mapper)

Pemakaian:

@JsonConvert.register
class Employee(object):
    def __init__(self, Name:int=None, Age:int=None):
        self.Name = Name
        self.Age = Age
        return

@JsonConvert.register
class Company(object):
    def __init__(self, Name:str="", Employees:[Employee]=None):
        self.Name = Name
        self.Employees = [] if Employees is None else Employees
        return

company = Company("Contonso")
company.Employees.append(Employee("Werner", 38))
company.Employees.append(Employee("Mary"))

as_json = JsonConvert.to_json(company)
from_json = JsonConvert.from_json(as_json)
as_json_from_json = JsonConvert.to_json(from_json)

assert(as_json_from_json == as_json)

print(as_json_from_json)
enazar
sumber
2

Memperluas jawaban DS sedikit, jika Anda membutuhkan objek agar bisa berubah (yang namesuple tidak), Anda dapat menggunakan perpustakaan recordclass alih-alih namedtuple:

import json
from recordclass import recordclass

data = '{"name": "John Smith", "hometown": {"name": "New York", "id": 123}}'

# Parse into a mutable object
x = json.loads(data, object_hook=lambda d: recordclass('X', d.keys())(*d.values()))

Objek yang dimodifikasi kemudian dapat dikonversi kembali ke json dengan sangat mudah menggunakan simplejson :

x.name = "John Doe"
new_json = simplejson.dumps(x)
BeneStr
sumber
1

Jika Anda menggunakan Python 3.6 atau yang lebih baru, Anda bisa melihat squema - modul ringan untuk struktur data yang diketik secara statis. Itu membuat kode Anda mudah dibaca sementara pada saat yang sama memberikan validasi data sederhana, konversi dan serialisasi tanpa kerja ekstra. Anda bisa menganggapnya sebagai alternatif yang lebih canggih dan beralasan untuk namtuple dan dataclasses. Inilah cara Anda dapat menggunakannya:

from uuid import UUID
from squema import Squema


class FbApiUser(Squema):
    id: UUID
    age: int
    name: str

    def save(self):
        pass


user = FbApiUser(**json.loads(response))
user.save()
Funkatik
sumber
Ini juga lebih mirip dengan cara bahasa JVM untuk melakukannya.
javadba
1

Saya sedang mencari solusi yang berhasil recordclass.RecordClass , mendukung objek bersarang dan bekerja untuk serialisasi json dan deserialisasi json.

Memperluas jawaban DS, dan memperluas solusi dari BeneStr, saya menghasilkan hal-hal berikut yang tampaknya berhasil:

Kode:

import json
import recordclass

class NestedRec(recordclass.RecordClass):
    a : int = 0
    b : int = 0

class ExampleRec(recordclass.RecordClass):
    x : int       = None
    y : int       = None
    nested : NestedRec = NestedRec()

class JsonSerializer:
    @staticmethod
    def dumps(obj, ensure_ascii=True, indent=None, sort_keys=False):
        return json.dumps(obj, default=JsonSerializer.__obj_to_dict, ensure_ascii=ensure_ascii, indent=indent, sort_keys=sort_keys)

    @staticmethod
    def loads(s, klass):
        return JsonSerializer.__dict_to_obj(klass, json.loads(s))

    @staticmethod
    def __obj_to_dict(obj):
        if hasattr(obj, "_asdict"):
            return obj._asdict()
        else:
            return json.JSONEncoder().default(obj)

    @staticmethod
    def __dict_to_obj(klass, s_dict):
        kwargs = {
            key : JsonSerializer.__dict_to_obj(cls, s_dict[key]) if hasattr(cls,'_asdict') else s_dict[key] \
                for key,cls in klass.__annotations__.items() \
                    if s_dict is not None and key in s_dict
        }
        return klass(**kwargs)

Pemakaian:

example_0 = ExampleRec(x = 10, y = 20, nested = NestedRec( a = 30, b = 40 ) )

#Serialize to JSON

json_str = JsonSerializer.dumps(example_0)
print(json_str)
#{
#  "x": 10,
#  "y": 20,
#  "nested": {
#    "a": 30,
#    "b": 40
#  }
#}

# Deserialize from JSON
example_1 = JsonSerializer.loads(json_str, ExampleRec)
example_1.x += 1
example_1.y += 1
example_1.nested.a += 1
example_1.nested.b += 1

json_str = JsonSerializer.dumps(example_1)
print(json_str)
#{
#  "x": 11,
#  "y": 21,
#  "nested": {
#    "a": 31,
#    "b": 41
#  }
#}
Shriram V
sumber
1

Jawaban yang diberikan di sini tidak mengembalikan tipe objek yang benar, maka saya membuat metode ini di bawah ini. Mereka juga gagal jika Anda mencoba menambahkan lebih banyak bidang ke kelas yang tidak ada di JSON yang diberikan:

def dict_to_class(class_name: Any, dictionary: dict) -> Any:
    instance = class_name()
    for key in dictionary.keys():
        setattr(instance, key, dictionary[key])
    return instance


def json_to_class(class_name: Any, json_string: str) -> Any:
    dict_object = json.loads(json_string)
    return dict_to_class(class_name, dict_object)
Caner
sumber
0

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
0

Kamu bisa memakai

x = Map(json.loads(response))
x.__class__ = MyClass

dimana

class Map(dict):
    def __init__(self, *args, **kwargs):
        super(Map, self).__init__(*args, **kwargs)
        for arg in args:
            if isinstance(arg, dict):
                for k, v in arg.iteritems():
                    self[k] = v
                    if isinstance(v, dict):
                        self[k] = Map(v)

        if kwargs:
            # for python 3 use kwargs.items()
            for k, v in kwargs.iteritems():
                self[k] = v
                if isinstance(v, dict):
                    self[k] = Map(v)

    def __getattr__(self, attr):
        return self.get(attr)

    def __setattr__(self, key, value):
        self.__setitem__(key, value)

    def __setitem__(self, key, value):
        super(Map, self).__setitem__(key, value)
        self.__dict__.update({key: value})

    def __delattr__(self, item):
        self.__delitem__(item)

    def __delitem__(self, key):
        super(Map, self).__delitem__(key)
        del self.__dict__[key]

Untuk solusi generik, bukti masa depan.

Gulzar
sumber
-5

Gunakan jsonmodul ( baru dalam Python 2.6 ) atau simplejsonmodul yang hampir selalu diinstal.

Chris Morgan
sumber
2
Hei, terima kasih sudah membalas. Bisakah Anda memposting contoh cara mendekode JSON dan kemudian mengakses data itu?
Sai Krishna
Hei, sekarang Anda ada benarnya tetapi entah bagaimana, saya lebih suka melakukannya tanpa mengetahui dan kemudian melakukan rekayasa ulang: D.
Sai Krishna
1
@ Zak: ada contoh tepat di bagian atas dokumen yang saya tautkan.
Chris Morgan