Tuple adlı bir Python'u json'a seri hale getirme


87

namedtupleAlan adları korunarak a'dan json'a serileştirmenin önerilen yolu nedir ?

A'dan namedtuplejson'a serileştirmek, yalnızca değerlerin serileştirilmesine ve alan adlarının çeviride kaybolmasına neden olur. Alanların jsonize edildiğinde de korunmasını istiyorum ve dolayısıyla aşağıdakileri yaptım:

class foobar(namedtuple('f', 'foo, bar')):
    __slots__ = ()
    def __iter__(self):
        yield self._asdict()

Yukarıdaki, beklediğim gibi json'a serileştirir ve yinelerken namedtupletuple benzeri olmayan sonuçlar dışında kullandığım diğer yerlerde (öznitelik erişimi vb.) Olduğu gibi davranır (bu benim kullanım durumum için uygundur).

Alan adları korunarak json'a dönüştürmenin "doğru yolu" nedir?


Yanıtlar:


55

Bu oldukça zordur, çünkü namedtuple()türetilmiş yeni bir türü döndüren bir fabrika tuple. Bir yaklaşım, sınıfınızın da miras almasını sağlamak olabilir UserDict.DictMixin, ancak tuple.__getitem__zaten tanımlanmıştır ve özelliğinin adını değil, öğenin konumunu belirten bir tam sayı beklemektedir:

>>> f = foobar('a', 1)
>>> f[0]
'a'

Adlandırılmış tup, temelde JSON için garip bir uyumdur, çünkü anahtar adlarının örnek içinde depolandığı bir sözlüğün aksine, anahtar adları tür tanımının bir parçası olarak sabitlenen özel olarak oluşturulmuş bir türdür . Bu, adlandırılmış bir grubu "geri döndürmenizi" engeller, örneğin, bir sözlüğün kodunu başka bir bilgi parçası olmadan adlandırılmış bir gruba geri çözemezsiniz {'a': 1, '#_type': 'foobar'}.

Bu ideal değildir, ancak yalnızca adlandırılmış çiftleri sözlüklere kodlamanız gerekiyorsa , başka bir yaklaşım da JSON kodlayıcınızı bu türlere özel duruma göre genişletmek veya değiştirmektir. İşte Python'un alt sınıflamasına bir örnek json.JSONEncoder. Bu, yuvalanmış adlandırılmış tabloların doğru şekilde sözlüğe dönüştürülmesini sağlama sorununu çözer:

from collections import namedtuple
from json import JSONEncoder

class MyEncoder(JSONEncoder):

    def _iterencode(self, obj, markers=None):
        if isinstance(obj, tuple) and hasattr(obj, '_asdict'):
            gen = self._iterencode_dict(obj._asdict(), markers)
        else:
            gen = JSONEncoder._iterencode(self, obj, markers)
        for chunk in gen:
            yield chunk

class foobar(namedtuple('f', 'foo, bar')):
    pass

enc = MyEncoder()
for obj in (foobar('a', 1), ('a', 1), {'outer': foobar('x', 'y')}):
    print enc.encode(obj)

{"foo": "a", "bar": 1}
["a", 1]
{"outer": {"foo": "x", "bar": "y"}}

12
Adlandırılmış tup, temelde JSON için garip bir uyumdur, çünkü anahtar adlarının örnek içinde depolandığı bir sözlüğün aksine, anahtar adları tür tanımının bir parçası olarak sabitlenen özel olarak oluşturulmuş bir türdür. Çok anlayışlı bir yorum. Bunu düşünmemiştim. Teşekkürler. Adlandırılmış çiftleri , öznitelik adlandırma kolaylığı ile hoş bir değişmez yapı sağladıkları için seviyorum . Cevabınızı kabul edeceğim. Bununla birlikte, Java'nın serileştirme mekanizması nesnenin nasıl serileştirildiği üzerinde daha fazla kontrol sağlar ve bu tür kancaların Python'da neden var olmadığını merak ediyorum.
calvinkrishy

Bu benim ilk yaklaşımımdı, ama aslında işe yaramıyor (zaten benim için).
zeekay

1
>>> json.dumps(foobar('x', 'y'), cls=MyEncoder) <<< '["x", "y"]'
zeekay

19
Ah, python 2.7+ _iterencode artık bir JSONEncoder yöntemi değil.
zeekay

2
@calvin Teşekkürler, isimli tabloyu da faydalı buluyorum, keşke tekrarlı olarak JSON'a kodlamak için daha iyi bir çözüm olsaydı. @zeekay Evet, 2.7+ sürümünde görünüyorlar, bunu saklıyorlar, böylece artık geçersiz kılınamaz. Bu hayal kırıklığı yaratıyor.
samplebias

77

namedtupleSerileştirmek istediğiniz yalnızca bir taneyse, _asdict()yöntemini kullanmak işe yarayacaktır (Python> = 2.7 ile)

>>> from collections import namedtuple
>>> import json
>>> FB = namedtuple("FB", ("foo", "bar"))
>>> fb = FB(123, 456)
>>> json.dumps(fb._asdict())
'{"foo": 123, "bar": 456}'

4
AttributeError alıyorum: Windows üzerinde Python 2.7 (x64) ile bu kodu çalıştırırken 'FB' nesnesinin ' dict ' niteliği yok . Ancak fb._asdict () iyi çalışıyor.
geographika

5
fb._asdict()ya vars(fb)da daha iyi olurdu.
jpmc26

1
@ jpmc26: varsBir nesnede bir __dict__.
Rufflewind

@Rufflewind Bunlarda da kullanamazsınız __dict__. =)
jpmc26

4
Python 3'te __dict__kaldırıldı. _asdicther ikisi üzerinde de çalışıyor gibi görünüyor.
Andy Hayden

21

Görünüşe göre simplejson.JSONEncoderbu işi yapmak için alt sınıfa girebiliyordunuz, ancak en son simplejson koduyla artık durum böyle değil: proje kodunu gerçekten değiştirmeniz gerekiyor. Simplejson'ın adlandırılmış çiftleri desteklememesi için hiçbir neden göremiyorum, bu yüzden projeyi çatalladım, adlandırılmış tuple desteği ekledim ve şu anda şubemin ana projeye geri çekilmesini bekliyorum . Şimdi düzeltmeye ihtiyacınız varsa, çatalımdan çekin.

DÜZENLEME : Görünüşe göre, simplejsonşimdi en son sürümleri namedtuple_as_objectbunu varsayılan olarak seçeneği ile yerel olarak destekliyor True.


3
Düzenlemeniz doğru cevaptır. simplejson, adlandırılmış çiftleri json'dan farklı bir şekilde (benim fikrim: daha iyi) seri hale getirir. Bu gerçekten şu kalıbı yapar: "try: import simplejson'ı json hariç: import json", bazı makinelerde simplejson'ın kurulu olup olmadığına bağlı olarak farklı davranışlar sergileyebileceğiniz için risklidir. Bu nedenle, şimdi birçok kurulum dosyamda simplejson'a ihtiyacım var ve bu kalıptan kaçınıyorum.
marr75

1
@ marr75 - ujsonBu tür uç durumlarda daha da tuhaf ve öngörülemez olan Ditto ...
mac

Aşağıdakileri kullanarak (oldukça basılmış) json'a serileştirilmiş özyinelemeli bir adlandırılmış çift elde edebildim:simplejson.dumps(my_tuple, indent=4)
KFL

5

Bunu yapmak için bir kitaplık yazdım: https://github.com/ltworf/typedload

Adlandırılmış tuple ve geri gidebilir.

Listeler, kümeler, numaralandırmalar, birleşimler, varsayılan değerler ile oldukça karmaşık iç içe geçmiş yapıları destekler. En yaygın durumları kapsamalıdır.

edit: Kitaplık ayrıca veri sınıfı ve attr sınıflarını da destekler.


2

Yinelemeli olarak adlandırılmışTuple verilerini json'a dönüştürür.

print(m1)
## Message(id=2, agent=Agent(id=1, first_name='asd', last_name='asd', mail='2@mai.com'), customer=Customer(id=1, first_name='asd', last_name='asd', mail='2@mai.com', phone_number=123123), type='image', content='text', media_url='h.com', la=123123, ls=4512313)

def reqursive_to_json(obj):
    _json = {}

    if isinstance(obj, tuple):
        datas = obj._asdict()
        for data in datas:
            if isinstance(datas[data], tuple):
                _json[data] = (reqursive_to_json(datas[data]))
            else:
                 print(datas[data])
                _json[data] = (datas[data])
    return _json

data = reqursive_to_json(m1)
print(data)
{'agent': {'first_name': 'asd',
'last_name': 'asd',
'mail': '2@mai.com',
'id': 1},
'content': 'text',
'customer': {'first_name': 'asd',
'last_name': 'asd',
'mail': '2@mai.com',
'phone_number': 123123,
'id': 1},
'id': 2,
'la': 123123,
'ls': 4512313,
'media_url': 'h.com',
'type': 'image'}

1
+1 Neredeyse aynısını yaptım. Ama dönüşünüz json değil bir emirdir. "Not" a sahip olmalısınız ve nesnenizdeki bir değer bir boole ise, bu doğruya dönüştürülmeyecektir. Bence dikteye dönüştürmek daha güvenli, sonra json'a dönüştürmek için json.dumps kullanın.
Fred Laurent

2

Daha uygun bir çözüm ise dekoratör kullanmaktır (korumalı alanı kullanır _fields).

Python 2.7+:

import json
from collections import namedtuple, OrderedDict

def json_serializable(cls):
    def as_dict(self):
        yield OrderedDict(
            (name, value) for name, value in zip(
                self._fields,
                iter(super(cls, self).__iter__())))
    cls.__iter__ = as_dict
    return cls

#Usage:

C = json_serializable(namedtuple('C', 'a b c'))
print json.dumps(C('abc', True, 3.14))

# or

@json_serializable
class D(namedtuple('D', 'a b c')):
    pass

print json.dumps(D('abc', True, 3.14))

Python 3.6.6+:

import json
from typing import TupleName

def json_serializable(cls):
    def as_dict(self):
        yield {name: value for name, value in zip(
            self._fields,
            iter(super(cls, self).__iter__()))}
    cls.__iter__ = as_dict
    return cls

# Usage:

@json_serializable
class C(NamedTuple):
    a: str
    b: bool
    c: float

print(json.dumps(C('abc', True, 3.14))

Bunu yapmayın, dahili API'yi her zaman değiştirirler. Typedload kütüphanemde farklı py sürümleri için birkaç durum var.
LtWorf

Evet, temiz. Ancak hiç kimse test yapmadan daha yeni bir Python sürümüne geçmemelidir. Ve diğer çözümler _asdictde "korumalı" bir sınıf üyesi olan kullanır.
Dmitry T.

1
LtWorf, kitaplığınız GPL ve frozensetler ile çalışmıyor
Thomas Grainger

2
@LtWorf Kitaplığınız ayrıca _fields;-) github.com/ltworf/typedload/blob/master/typedload/datadumper.py kullanıyor. Bu, adlandırılmış tuple'ın genel API'sinin bir parçasıdır, aslında: docs.python.org/3.7/library/… İnsanların kafası karışıyor alt çizgi (şaşmamalı!). Kötü bir tasarım, ama başka ne seçenekleri olduğunu bilmiyorum.
quant_dev

1
Ne şeyler? Ne zaman? Sürüm notlarından alıntı yapabilir misiniz?
quant_dev

2

Jsonplus kütüphane NamedTuple örnekleri için bir serileştirici sağlar. Gerekirse basit nesnelerin çıktısını almak için uyumluluk modunu kullanın, ancak kod çözmede yardımcı olduğu için varsayılanı tercih edin.


Buradaki diğer çözümlere baktım ve bu bağımlılığı eklemenin bana çok zaman kazandırdığını gördüm. Özellikle oturumda json olarak geçmem gereken bir NamedTuples listesine sahip olduğum için. jsonplus temelde adlandırılmış tuple listelerini json içine ve dışına almanıza izin verir .dumps()ve .loads()hiçbir yapılandırma çalışmaz .
Rob

1

Adlandırılmış çiftleri yerel python json kitaplığı ile doğru şekilde serileştirmek imkansızdır. Tupl'ları her zaman liste olarak görür ve bu davranışı değiştirmek için varsayılan serileştiriciyi geçersiz kılmak imkansızdır. Nesnelerin iç içe olması daha kötüdür.

Orjson gibi daha sağlam bir kitaplık kullanmak daha iyidir :

import orjson
from typing import NamedTuple

class Rectangle(NamedTuple):
    width: int
    height: int

def default(obj):
    if hasattr(obj, '_asdict'):
        return obj._asdict()

rectangle = Rectangle(width=10, height=20)
print(orjson.dumps(rectangle, default=default))

=>

{
    "width":10,
    "height":20
}

1
ben de hayranıyım orjson.
CircleOnCircles

0

Bu eski bir sorudur. Ancak:

Aynı soruyu soran herkes için bir öneri, NamedTupledaha önce sahip oldukları ve zamanla tekrar değişeceği için özel veya iç özelliklerinden herhangi birini kullanmayı dikkatlice düşünün .

Örneğin, sizin NamedTupledüz değerli bir nesneyse ve başka bir nesnenin içine yerleştirildiği durumlarda değil, yalnızca serileştirmekle ilgileniyorsanız, __dict__kaldırılma veya _as_dict()değişme ile ortaya çıkabilecek sorunlardan kaçınabilir ve sadece gibi bir şey yapabilirsiniz. (ve evet bu Python 3 çünkü bu cevap şimdilik):

from typing import NamedTuple

class ApiListRequest(NamedTuple):
  group: str="default"
  filter: str="*"

  def to_dict(self):
    return {
      'group': self.group,
      'filter': self.filter,
    }

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

Varsa aramayı yapmak için defaultaranabilir kwarg'ı kullanmayı denedim , ancak bu bir listeye dönüştürülebilir olduğu için çağrılmadı .dumpsto_dict()NamedTuple


3
_asdict, namedtuple public API'nin bir parçasıdır. Alt çizginin nedenini açıklar docs.python.org/3.7/library/… " Tuple'lardan miras alınan yöntemlere ek olarak, adlandırılmış tuples üç ek yöntemi ve iki özniteliği destekler. Alan adları, yöntem ve öznitelik adları ile çakışmaları önlemek için bir alt çizgiyle başlayın. "
quant_dev

@quant_dev teşekkürler, bu açıklamayı görmedim. API kararlılığının garantisi değildir, ancak bu yöntemlerin daha güvenilir olmasına yardımcı olur. Açıkça to_dict okunabilirliğini seviyorum, ancak _as_dict'i yeniden uygulamak gibi göründüğünü görebiliyorum
dlamblin

0

İşte benim problemi ele alıyorum. NamedTuple'ı serileştirir, katlanmış NamedTuple'ları ve bunların içindeki Listeleri yönetir.

def recursive_to_dict(obj: Any) -> dict:
_dict = {}

if isinstance(obj, tuple):
    node = obj._asdict()
    for item in node:
        if isinstance(node[item], list): # Process as a list
            _dict[item] = [recursive_to_dict(x) for x in (node[item])]
        elif getattr(node[item], "_asdict", False): # Process as a NamedTuple
            _dict[item] = recursive_to_dict(node[item])
        else: # Process as a regular element
            _dict[item] = (node[item])
return _dict

0

simplejson.dump()yerine json.dumpiş yapar. Yine de daha yavaş olabilir.

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.