Python namedtuple을 json으로 직렬화


85

namedtuple유지 된 필드 이름으로 json 에 직렬화하는 권장 방법은 무엇입니까 ?

a namedtuple를 json으로 직렬화하면 값이 직렬화되고 필드 이름이 변환에서 손실됩니다. 필드도 json-ized 때 유지되기를 원하므로 다음을 수행했습니다.

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

위의 내용은 내가 예상 한대로 json으로 직렬화하고 namedtuple내가 사용하는 다른 장소 (속성 액세스 등)에서 와 같이 동작 합니다. 단, 반복하는 동안 튜플과 같은 결과가 아닌 경우 (내 사용 사례에 적합)를 제외하고는 작동합니다.

필드 이름이 유지 된 상태에서 json으로 변환하는 "올바른 방법"은 무엇입니까?


답변:


56

namedtuple()에서 파생 된 새로운 유형을 반환하는 팩토리 이기 때문에 이것은 매우 까다 롭습니다 tuple. 한 가지 방법은 클래스도에서 상속하도록하는 UserDict.DictMixin것이지만 tuple.__getitem__이미 정의되어 있으며 속성 이름이 아닌 요소의 위치를 ​​나타내는 정수를 예상합니다.

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

기본적으로 namedtuple은 키 이름이 인스턴스 내부에 저장되는 사전과 달리 키 이름이 유형 정의의 일부로 고정 되는 사용자 지정 빌드 유형 이므로 JSON에 이상하게 맞습니다 . 이렇게하면 명명 된 튜플을 "왕복"하는 것을 방지 할 수 있습니다. 예를 들어 dict의 앱 특정 유형 마커와 같은 다른 정보 없이는 사전을 명명 된 튜플로 다시 디코딩 할 수 없습니다 {'a': 1, '#_type': 'foobar'}.

이것은 이상적이지는 않지만 명명 된 튜플을 사전 으로 인코딩하기 만하면되는 경우 다른 접근 방식은 JSON 인코더를 이러한 유형의 특수한 경우로 확장하거나 수정하는 것입니다. 다음은 Python을 서브 클래 싱하는 예입니다 json.JSONEncoder. 이렇게하면 중첩 된 명명 된 튜플이 사전으로 제대로 변환되는지 확인하는 문제가 해결됩니다.

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
기본적으로 namedtuple은 키 이름이 인스턴스 내부에 저장되는 사전과 달리 키 이름이 유형 정의의 일부로 고정되는 사용자 지정 빌드 유형이기 때문에 JSON에 이상하게 적합합니다. 매우 통찰력있는 코멘트. 나는 그것에 대해 생각하지 않았습니다. 감사. 명명 된 튜플 속성 명명 편의성 과 함께 멋진 불변 구조 제공하기 때문에 좋아 합니다. 나는 당신의 대답을 받아 들일 것입니다. 즉, 자바의 직렬화 메커니즘이 더 이상 제어 제공했다 가졌 방법 객체 직렬화하고 나는 그런 후크 파이썬에 존재하지 않는 것 같습니다 이유를 알고 궁금합니다.
calvinkrishy

그것은 나의 첫 번째 접근 방식 이었지만 실제로는 작동하지 않습니다 (어쨌든 나를 위해).
zeekay

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

19
아, 파이썬 2.7+에서 _iterencode는 더 이상 JSONEncoder의 메소드가 아닙니다.
zeekay

2
@calvin 감사합니다. namedtuple도 유용하다고 생각합니다. JSON으로 재귀 적으로 인코딩하는 더 나은 솔루션이 있기를 바랍니다. @zeekay 그래, 2.7+에서 그들은 그것을 숨기므로 더 이상 무시할 수 없습니다. 실망 스럽네요.
samplebias

77

namedtuple직렬화하려는 경우에만 해당 _asdict()메서드를 사용 하면 작동합니다 (Python> = 2.7 사용).

>>> 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 : 'FB'object has no attribute ' dict'when running that code in Python 2.7 (x64) on Windows. 그러나 fb._asdict ()는 잘 작동합니다.
geographika

5
fb._asdict()아니면 vars(fb)더 좋을 것입니다.
jpmc26

1
@ jpmc26 : 당신은 사용할 수 없습니다 vars없이 객체 __dict__.
Rufflewind

@Rufflewind 당신은 __dict__그들 에도 사용할 수 없습니다 . =)
jpmc26

4
파이썬 3 __dict__에서는 제거되었습니다. _asdict둘 다 작동하는 것으로 보입니다.
앤디 헤이든

21

simplejson.JSONEncoder이 작업을 수행하기 위해 하위 클래스 를 만들 수 있었던 것처럼 보이지만 최신 simplejson 코드에서는 더 이상 그렇지 않습니다. 실제로 프로젝트 코드를 수정해야합니다. simplejson이 namedtuple을 지원하지 않아야 할 이유가 없으므로 프로젝트를 분기하고 namedtuple 지원을 추가했으며 현재 브랜치가 메인 프로젝트로 돌아 오기를 기다리고 있습니다 . 지금 수정이 필요하면 내 포크에서 가져 오세요.

편집 : 최신 버전처럼 보인다는 simplejson이제 기본적으로 이것을 지원 namedtuple_as_object되는 디폴트 옵션 True.


3
귀하의 편집이 정답입니다. simplejson은 json과 다르게 namedtuple을 직렬화합니다 (내 의견 : 더 좋음). 이것은 정말로 다음과 같은 패턴을 만듭니다 : "try : import simplejson as json except : import json", simplejson이 설치되었는지 여부에 따라 일부 머신에서 다른 동작을 얻을 수 있기 때문에 위험합니다. 이러한 이유로 이제는 많은 설정 파일에 simplejson이 필요하고 해당 패턴을 사용하지 않습니다.
marr75

1
@ marr75- ujson같은 상황에서 훨씬 더 기괴하고 예측할 수없는에 대한 Ditto ...
mac

다음을 사용하여 (예쁜 인쇄 된) json으로 직렬화 된 재귀 namedtuple을 얻을 수있었습니다.simplejson.dumps(my_tuple, indent=4)
KFL

5

이 작업을 수행하기 위해 라이브러리를 작성했습니다 : https://github.com/ltworf/typedload

명명 된 튜플 사이를 오가고 돌아갈 수 있습니다.

목록, 집합, 열거 형, 공용체, 기본값이있는 매우 복잡한 중첩 구조를 지원합니다. 대부분의 일반적인 경우를 다루어야합니다.

편집 : 라이브러리는 데이터 클래스 및 속성 클래스도 지원합니다.


2

namedTuple 데이터를 재귀 적으로 json으로 변환합니다.

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 거의 똑같이 만들었습니다. 그러나 귀하의 반환은 json이 아닌 사전입니다. "not '이 있어야하며 객체의 값이 부울이면 true로 변환되지 않습니다. dict로 변환 한 다음 json.dumps를 사용하여 json으로 변환하는 것이 더 안전하다고 생각합니다.
Fred Laurent

2

더 편리한 해결책은 데코레이터를 사용하는 것입니다 (보호 된 필드를 사용합니다 _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))

그렇게하지 마십시오. 그들은 항상 내부 API를 변경합니다. 내 typedload 라이브러리에는 다른 py 버전에 대한 몇 가지 사례가 있습니다.
LtWorf

예, 분명합니다. 그러나 누구도 테스트없이 최신 Python 버전으로 마이그레이션해서는 안됩니다. 그리고 다른 솔루션 _asdict은 "보호 된"클래스 멤버이기도 한를 사용합니다.
Dmitry T.

1
LtWorf이 라이브러리는 GPL과 frozensets 작동하지 않습니다
토마스 그레인저

2
@LtWorf 라이브러리는 _fields;-) github.com/ltworf/typedload/blob/master/typedload/datadumper.py를 사용 합니다. 실제로 namedtuple의 공용 API의 일부입니다. 실제로 : docs.python.org/3.7/library/… 사람들은 다음과 같이 혼란스러워합니다. 밑줄 (당연합니다!). 나쁜 디자인이지만 그들이 어떤 다른 선택을했는지 모르겠습니다.
quant_dev

1
무슨 일? 언제? 릴리스 노트를 인용 할 수 있습니까?
quant_dev dec.

2

jsonplus의 라이브러리는 NamedTuple 인스턴스들에 대한 serializer를 제공합니다. 필요한 경우 호환성 모드를 사용하여 간단한 개체를 출력하지만 다시 디코딩하는 데 도움이되는 기본값을 선호합니다.


여기에서 다른 솔루션을 살펴본 결과이 종속성을 추가하는 것만으로도 많은 시간을 절약 할 수 있다는 것을 알았습니다. 특히 세션에서 json으로 전달해야하는 NamedTuples 목록이 있기 때문입니다. jsonplus를 사용하면 기본적으로 이름이 지정된 튜플 목록을 json 안팎으로 가져올 수 .dumps()있으며 .loads()구성 없이도 작동합니다.
Rob

1

네이티브 python json 라이브러리로 namedtuple을 올바르게 직렬화하는 것은 불가능합니다. 튜플은 항상 목록으로 간주되며이 동작을 변경하기 위해 기본 직렬 변환기를 재정의하는 것은 불가능합니다. 개체가 중첩 된 경우 더 나쁩니다.

보다 강력한 라이브러리를 사용하는 것이 좋습니다. orjson 좋습니다 .

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
나도 팬이다 orjson.
CircleOnCircles

0

이것은 오래된 질문입니다. 하나:

같은 질문을 가진 모든 사람들을위한 제안입니다.의 사적 또는 내부 기능 중 하나를 사용하는 것에 대해 신중하게 생각 NamedTuple하십시오. 이전에 있었으며 시간이 지남에 따라 다시 변경 될 것입니다.

예를 들어, NamedTuple플랫 값 객체이고 직렬화에만 관심이 있고 다른 객체에 중첩되는 경우가 아니라면 __dict__제거되거나 _as_dict()변경 될 때 발생할 수있는 문제를 피하고 다음과 같은 작업을 수행 할 수 있습니다. (그리고 예,이 답변은 현재를위한 것이기 때문에 파이썬 3입니다) :

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())

가능한 경우 호출 을 수행하기 위해 default호출 가능한 kwarg 를 사용하려고 시도했지만dumpsto_dict()NamedTuple 목록으로 변환 할 수 있으므로 .


3
_asdictnamedtuple 공용 API의 일부입니다. 밑줄에 대한 이유를 설명합니다. docs.python.org/3.7/library/… "튜플에서 상속 된 메소드 외에도 명명 된 튜플은 3 개의 추가 메소드와 2 개의 속성을 지원합니다. 필드 이름과의 충돌을 방지하기 위해 메소드 및 속성 이름 밑줄로 시작하세요. "
quant_dev dec.

@quant_dev 감사합니다, 나는 그 설명을 보지 못했습니다. API 안정성을 보장하지는 않지만 이러한 메서드를 더 신뢰할 수있게 만드는 데 도움이됩니다. 나는 명시 적 to_dict 가독성 좋아한다, 그러나 나는 다시 구현 _as_dict 것 같아 볼 수 있습니다
dlamblin

0

여기에 문제가 있습니다. NamedTuple을 직렬화하고 접힌 NamedTuple 및 그 안의 목록을 처리합니다.

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()대신 json.dump일을합니다. 그래도 느릴 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.