Bir Enum üyesini JSON'a seri hale getirme


96

Bir Python Enumüyesini JSON'a nasıl serileştirebilirim, böylece ortaya çıkan JSON'u tekrar bir Python nesnesine seri durumdan çıkarabilirim?

Örneğin, bu kod:

from enum import Enum    
import json

class Status(Enum):
    success = 0

json.dumps(Status.success)

hataya neden olur:

TypeError: <Status.success: 0> is not JSON serializable

Bundan nasıl kaçınabilirim?

Yanıtlar:


52

Rasgele bir enum.Enumüyeyi JSON'a kodlamak ve ardından aynı enum üyesi olarak (yalnızca enum üyesinin valueözniteliği yerine) kodunu çözmek istiyorsanız, bunu özel bir JSONEncodersınıf ve object_hookbağımsız değişken olarak iletilecek bir kod çözme işlevi yazarak yapabilirsiniz json.load()veya json.loads():

PUBLIC_ENUMS = {
    'Status': Status,
    # ...
}

class EnumEncoder(json.JSONEncoder):
    def default(self, obj):
        if type(obj) in PUBLIC_ENUMS.values():
            return {"__enum__": str(obj)}
        return json.JSONEncoder.default(self, obj)

def as_enum(d):
    if "__enum__" in d:
        name, member = d["__enum__"].split(".")
        return getattr(PUBLIC_ENUMS[name], member)
    else:
        return d

as_enumFonksiyon kullanılarak kodlanmış olan JSON dayanır EnumEncoderbuna aynı şekilde davranır, ya da bir şey.

Üyelerine kısıtlama, PUBLIC_ENUMSkötü amaçla oluşturulmuş bir metnin, örneğin, özel bilgileri (örneğin, uygulama tarafından kullanılan gizli bir anahtar) ilgisiz bir veritabanı alanına kaydedilmesi için kandırarak arama kodunu kullanmaktan kaçınmak için gereklidir. (bkz. http://chat.stackoverflow.com/transcript/message/35999686#35999686 ).

Örnek kullanım:

>>> data = {
...     "action": "frobnicate",
...     "status": Status.success
... }
>>> text = json.dumps(data, cls=EnumEncoder)
>>> text
'{"status": {"__enum__": "Status.success"}, "action": "frobnicate"}'
>>> json.loads(text, object_hook=as_enum)
{'status': <Status.success: 0>, 'action': 'frobnicate'}

1
Teşekkürler Sıfır! Güzel örnek.
Ethan Furman

Kodunuz bir modülde (örneğin enumencoder.py) varsa, JSON'dan ayrıştırdığınız sınıfı dict'e aktarmanız gerekir. Örneğin, bu durumda, enumencoder.py modülündeki Durum sınıfını içe aktarmanız gerekir .
Francisco Manuel Garca Botella

Benim endişem kötü niyetli arama kodu değil, bir web sunucusuna kötü niyetli isteklerdi. Bahsettiğiniz gibi, özel veriler bir yanıtta açığa çıkarılabilir veya kod akışını değiştirmek için kullanılabilir. Cevabınızı güncellediğiniz için teşekkür ederiz. Yine de ana kod örneği güvenli olsaydı daha da iyi olurdu.
Jared Deckard

1
@JaredDeckard özür dilerim, haklıydın ve ben yanılmışım. Cevabı buna göre güncelledim. Girdiniz için teşekkürler! Bu eğitici (ve cezalandırıcı) oldu.
Zero Piraeus

bu seçenek daha uygun olur if isinstance(obj, Enum):mu?
user7440787

114

Bunun eski olduğunu biliyorum ama bunun insanlara yardımcı olacağını düşünüyorum. Tam olarak bu sorunu aştım ve dize numaralandırmaları kullanıp kullanmadığınızı keşfettim, numaralandırmalarınızı strhemen hemen tüm durumlar için işe yarayan bir alt sınıf olarak ilan ettim :

import json
from enum import Enum

class LogLevel(str, Enum):
    DEBUG = 'DEBUG'
    INFO = 'INFO'

print(LogLevel.DEBUG)
print(json.dumps(LogLevel.DEBUG))
print(json.loads('"DEBUG"'))
print(LogLevel('DEBUG'))

Çıktı:

LogLevel.DEBUG
"DEBUG"
DEBUG
LogLevel.DEBUG

Gördüğünüz gibi, JSON'un yüklenmesi dizeyi çıktılar, DEBUGancak kolayca bir LogLevel nesnesine geri dönüştürülebilir. Özel bir JSONEncoder oluşturmak istemiyorsanız iyi bir seçenek.


1
Teşekkürler. Çoğunlukla birden fazla mirasa karşı olsam da, bu oldukça temiz ve ben de böyle yapıyorum. Ekstra kodlayıcıya gerek yok :)
Vinicius Dantas

@madjardi, yaşadığınız sorunu detaylandırır mısınız? Dizenin değerinin, numaralandırmadaki özniteliğin adından farklı olmasıyla ilgili hiçbir sorun yaşamadım. Yorumunuzu yanlış mı anlıyorum?
Justin Carter

1
class LogLevel(str, Enum): DEBUG = 'Дебаг' INFO = 'Инфо'bu durumda enum with strdüzgün çalışmaz (
madjardi

1
Bu numarayı diğer temel türlerle de yapabilirsiniz, örneğin (bunu yorumlarda nasıl biçimlendireceğimi bilmiyorum, ancak ana fikir açık: "class Shapes (int, Enum): square = 1 circle = 2" çalışır harika bir kodlayıcıya gerek yok. Teşekkürler, bu harika bir yaklaşım!
NoCake

71

Doğru cevap, serileştirilmiş sürümle ne yapmak istediğinize bağlıdır.

Python'a geri dönecekseniz, Zero'nun cevabına bakın .

Serileştirilmiş sürümünüz başka bir dile gidiyorsa, muhtemelen IntEnumbunun yerine, karşılık gelen tamsayı olarak otomatik olarak serileştirilen bir kullanmak isteyeceksiniz :

from enum import IntEnum
import json

class Status(IntEnum):
    success = 0
    failure = 1

json.dumps(Status.success)

ve bu şunu döndürür:

'0'

5
@AShelly: Soru etiketlendi Python3.4ve bu yanıt 3.4+ özeldir.
Ethan Furman

2
Mükemmel. Enum bir EnumMetaIntEnum
dizeyse,

5
@bholagabbar: Hayır, Enummuhtemelen bir strmixin ile kullanırsınız -class MyStrEnum(str, Enum): ...
Ethan Furman

3
@bholagabbar, ilginç. Çözümünüzü bir cevap olarak göndermelisiniz.
Ethan Furman

1
EnumMetaYalnızca bir meta sınıf olması amaçlanan öğeden doğrudan miras almaktan kaçınırdım. Bunun yerine, not uygulaması olduğunu IntEnum tek liner olan ve aynı elde edebilirsiniz strsahip class StrEnum(str, Enum): ....
yungchin

15

Python 3.7'de, sadece kullanabilir json.dumps(enum_obj, default=str)


Güzel görünüyor ama nameenum değerini json dizesine yazacak . Daha iyi bir yol value, numaralandırmanın kullanılması olacaktır .
eNca

Enum değeri tarafından kullanılabilirjson.dumps(enum_obj, default=lambda x: x.value)
eNca

10

Zero Piraeus'un cevabını beğendim, ancak Boto olarak bilinen Amazon Web Services (AWS) için API ile çalışmak üzere biraz değiştirdim.

class EnumEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, Enum):
            return obj.name
        return json.JSONEncoder.default(self, obj)

Daha sonra bu yöntemi veri modelime ekledim:

    def ToJson(self) -> str:
        return json.dumps(self.__dict__, cls=EnumEncoder, indent=1, sort_keys=True)

Umarım bu birine yardımcı olur.


ToJsonVeri modelinize neden eklemeniz gerekiyor ?
Yu Chen

2

jsonpickleEn kolay yolu kullanıyorsanız aşağıdaki gibi bakmalısınız.

from enum import Enum
import jsonpickle


@jsonpickle.handlers.register(Enum, base=True)
class EnumHandler(jsonpickle.handlers.BaseHandler):

    def flatten(self, obj, data):
        return obj.value  # Convert to json friendly format


if __name__ == '__main__':
    class Status(Enum):
        success = 0
        error = 1

    class SimpleClass:
        pass

    simple_class = SimpleClass()
    simple_class.status = Status.success

    json = jsonpickle.encode(simple_class, unpicklable=False)
    print(json)

Json serileştirme sonra beklendiği gibi olacak {"status": 0}yerine

{"status": {"__objclass__": {"py/type": "__main__.Status"}, "_name_": "success", "_value_": 0}}

-1

Bu benim için çalıştı:

class Status(Enum):
    success = 0

    def __json__(self):
        return self.value

Başka hiçbir şeyi değiştirmek zorunda kalmadı. Açıkçası, bundan yalnızca değeri elde edeceksiniz ve serileştirilmiş değeri daha sonra numaralandırmaya geri dönüştürmek istiyorsanız başka bir iş yapmanız gerekecek.


2
Dokümanlarda bu sihirli yöntemi açıklayan hiçbir şey görmüyorum . Başka bir JSON kitaplığı mı kullanıyorsunuz yoksa bir JSONEncoderyerde özel bir özelliğiniz var mı?
0x5453
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.