Sınıf örneğini JSON'a seri hale getirme


186

Sınıf örneği bir JSON dize temsili oluşturmaya çalışıyorum ve zorluk yaşıyorum. Diyelim ki sınıf şöyle inşa edildi:

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

Json.dumps çağrısı şu şekilde yapılır:

t = testclass()
json.dumps(t)

Başarısız ve test sınıfının JSON serileştirilebilir olmadığını söylüyor.

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

Ayrıca turşu modülünü kullanmayı denedim:

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

Sınıf örneği bilgileri verir, ancak sınıf örneğinin serileştirilmiş bir içeriğini vermez.

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

Neyi yanlış yapıyorum?



30
Bir satır kullanın s = json.dumps(obj, default=lambda x: x.__dict__)serialize nesnenin örnek değişkenlerine, ( self.value1, self.value2, ...). Bu en basit ve en basit yol. Yuvalanmış nesne yapılarını serileştirir. defaultFonksiyonu, herhangi bir nesne, doğrudan seri değilken adı verilir. Aşağıdaki yanıtı da görebilirsiniz. Popüler cevapları gereksiz yere karmaşık buldum, ki bu muhtemelen uzun zaman önce doğruydu.
codeman48

1
Sizin testclasshiç sahip __init__()tüm örnekler aynı iki sınıf özelliklerini (paylaşacak böylece, yöntem value1ve value2sınıf açıklamada tanımlanmıştır). Sınıf ve sınıf örneği arasındaki farkı anlıyor musunuz?
martineau

1
Bu github.com/jsonpickle/jsonpickle için bir python kütüphanesi var (cevap iş parçacığında çok aşağıda olduğundan ve ulaşılamayacağından yorum yapıyor.)
En iyi dileklerimle

Yanıtlar:


238

Temel sorun, JSON kodlayıcının json.dumps()varsayılan olarak, tüm yerleşik türlerin sınırlı bir dizi nesne türünü serileştirmeyi bilmesi. Burada listelenen : https://docs.python.org/3.3/library/json.html#encoders-and-decoders

İyi bir çözüm, sınıfınızı devralmak JSONEncoderve sonra JSONEncoder.default()işlevi uygulamak ve bu işlevin sınıfınız için doğru JSON yaymasını sağlamak olacaktır.

Basit bir çözüm aramak olacağını json.dumps()üzerindeki .__dict__o örneğe üyesi. Bu standart bir Python dictve sınıfınız basitse JSON serileştirilebilir olacak.

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}

Yukarıdaki yaklaşım bu blog yazısında tartışılmıştır:

    __Dict__ kullanarak rasgele Python nesnelerini JSON'a serileştirme


3
Bunu denedim. Json.dumps (t .__ dict__) çağrısının sonucu sadece {}.
ferhan

6
Bunun nedeni, sınıfınızda bir .__init__()yöntem işlevi bulunmamasıdır, bu nedenle sınıf örneğinizin boş bir sözlüğü vardır. Başka bir deyişle, {}örnek kodunuz için doğru sonuçtur.
steveha

3
Teşekkürler. Bu hile yapar. Ben hiçbir parametre ile basit bir init ekledim ve şimdi json.dumps (t .__ dict__) çağırmak şu formatta uygun veri döndürür: {"değer2": "345", "değer1": "123"} gibi mesajlar gördüm Bu daha önce, üyeler için özel bir serileştiriciye ihtiyacım olup olmadığından emin değildim, init'e ihtiyaç duyulmadığı açıkça belirtilmedi ya da özledim. Teşekkür ederim.
ferhan

3
Bu çalışma tek bir sınıf için değil, ilgili sınıf objeleriyle değil
Nwawel A Iroume

2
@NwawelAIroume: Doğru. Eğer örneğin bir listede birden çok nesne içeren bir nesne varsa hata halais not JSON serializable
gies0r

58

Denemek için benim için harika bir yol var:

json.dumps()bilinmeyen türler için özel bir serileştirici işlevi belirtebileceğiniz isteğe bağlı bir parametre varsayılanını alabilir.

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__

İlk iki ifs tarih ve saat serileştirme içindir ve sonra bir obj.__dict__ başka bir nesne için döndürülür.

son çağrı şöyle görünür:

json.dumps(myObj, default=serialize)

Bir koleksiyonu serileştirdiğinizde ve aramak istemediğinizde özellikle iyidir __dict__ her nesne için açıkça . İşte sizin için otomatik olarak yapılır.

Şimdiye kadar benim için çok iyi çalıştı, düşüncelerinizi dört gözle bekliyorum.


Anladım NameError: name 'serialize' is not defined. Herhangi bir ipucu?
Kyle Delaney

Çok hoş. Sadece yuvaları olan sınıflar için:try: dict = obj.__dict__ except AttributeError: dict = {s: getattr(obj, s) for s in obj.__slots__ if hasattr(obj, s)} return dict
fantezisi

Böyle popüler bir dilin bir nesneyi jsoniny için hiç astarı olmaması şaşırtıcı. Bunun statik olarak yazılmamış olması gerekir.
TheRennen

49

İşlevde defaultadlandırılmış parametreyi belirtebilirsiniz json.dumps():

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

Açıklama:

Dokümanları oluşturun ( 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.

(Python 2.7 ve Python 3.x üzerinde çalışır)

Not: Bu durumda , sorudaki örnek yapmaya çalıştığından değişkenlere instancedeğil classdeğişkenlere ihtiyacınız vardır . (Ben askerin class instancebir sınıf nesnesi olması gerektiğini düşünüyorum)

Ben @ phihag cevabı bu ilk öğrenilen burada . İşi yapmanın en basit ve en temiz yolu olarak bulundu.


6
Bu benim için çalıştı, ama datetime.date üyeleri nedeniyle biraz değiştirdim:default=lambda x: getattr(x, '__dict__', str(x))
Dakota Hawkins

@Dakota güzel çözüm; datetime.datebir C uygulaması olduğundan hiçbir __dict__özelliği yoktur . Tekdüzelik uğruna IMHO, datetime.datesahip olmalı ...
codeman48

22

Sadece yapıyorum:

data=json.dumps(myobject.__dict__)

Bu tam bir cevap değil ve bir çeşit karmaşık nesne sınıfınız varsa kesinlikle her şeyi alamazsınız. Ancak bunu basit nesnelerim için kullanıyorum.

Gerçekten iyi çalıştığı bir seçenek OptionParser modülünden aldığınız "seçenekler" sınıfı. Burada JSON isteğinin kendisi ile birlikte.

  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)

Eğer bunu bir sınıf içinde kullanmıyorsanız, kendini kaldırmak isteyebilirsiniz.
SpiRail

3
Nesne başka nesnelerden oluşmadığı sürece bu işe yarayacaktır.
Haroldo_OK

19

Jsonpickle kullanma

import jsonpickle

object = YourClass()
json_object = jsonpickle.encode(object)

5

JSON gerçekten keyfi Python nesnelerini serileştirmek için değildir. dictNesneleri serileştirmek için harika , ancak picklemodül genel olarak kullanmanız gereken şey. Çıktısı picklegerçekten insan tarafından okunabilir değildir, ancak gayet iyi bir şekilde çözülmelidir. JSON kullanmakta ısrar ederseniz jsonpickle, ilginç bir hibrit yaklaşım olan modülü kontrol edebilirsiniz .

https://github.com/jsonpickle/jsonpickle


9
Turşu ile gördüğüm ana sorun Python'a özgü bir format, JSON ise platformdan bağımsız bir format. Bir web uygulaması veya bazı mobil uygulamalar için arka uç yazıyorsanız JSON özellikle kullanışlıdır. Bu söylenmişti, jsonpickle'a işaret ettiğiniz için teşekkürler.
Haroldo_OK

@Haroldo_OK Jsonpickle hâlâ JSON'a ihracat yapmıyor, sadece insan tarafından okunamıyor mu?
Caelum

4

Burada, herhangi bir karmaşık olmayan sınıfın serileştirilmesi için iki basit işlev vardır, daha önce açıklandığı gibi fantezi değil.

Kod ayarlamaları olmadan sınıflara yeni üyeler ekleyebileceğim için bunu yapılandırma türü şeyler için kullanıyorum.

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}

3

Bunu yapmaya nasıl başlayacağınıza dair bazı iyi cevaplar var. Ancak akılda tutulması gereken bazı şeyler var:

  • Örnek büyük bir veri yapısında iç içe yerleştirilirse ne olur?
  • Sınıf adını da istiyorsa ne olur?
  • Örneğin örneğini serileştirmek isterseniz ne olur?
  • Ya __slots__yerine kullanıyorsan __dict__?
  • Ya sadece kendin yapmak istemezsen?

json-tricks , bunu bir süredir yapabilen (yaptığım ve diğerlerinin katkıda bulunduğu) bir kütüphanedir. Örneğin:

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)

Örneğini geri alacaksın. İşte json şöyle görünüyor:

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

Kendi çözümünüzü yapmak istiyorsanız, json-tricksbazı özel durumları (örneğin __slots__) unutmamak için kaynağına bakabilirsiniz .

Aynı zamanda, numpy dizileri, tarihler, karmaşık sayılar; ayrıca yorumlara izin verir.


3

Python3.x

Bildiğim kadarıyla ulaşabileceğim en iyi yaklaşım bu.
Bu kod tedavi set () de unutmayın.
Bu yaklaşım, sınıfın genişletilmesine ihtiyaç duyan jeneriktir (ikinci örnekte).
Sadece dosyalara yaptığımı unutmayın, ancak davranışı zevkinize göre değiştirmek kolaydır.

Ancak bu bir CoDec.

Biraz daha fazla çalışma ile sınıfınızı başka şekillerde inşa edebilirsiniz. Örnek bir varsayılan yapıcı varsayalım, o zaman sınıf diksiyon güncelleyin.

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)

Düzenle

Biraz daha araştırma yaparak , bir metasınıf kullanarak SUPERCLASS kayıt yöntemi çağrısına ihtiyaç duymadan genellemenin bir yolunu buldum

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

2

Kabul edilen cevapta önerildiği gibi miras yerine, polimorfizm kullanmak daha iyidir. Aksi takdirde, her nesnenin kodlamasını özelleştirmek için büyük bir if ifadesine sahip olmanız gerekir. Bu JSON için genel bir varsayılan kodlayıcı oluşturmak anlamına gelir:

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

ve sonra jsonEnc()her sınıfta serileştirmek istediğiniz bir işleve sahip olun. Örneğin

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

Sonra ara json.dumps(classInstance,default=jsonDefEncoder)

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.