JSON이 세트를 직렬화하는 방법은 무엇입니까?


149

나는 파이썬이 set가진 개체를 포함 __hash__하고 __eq__특정없고 중복을하기 위해 방법이 컬렉션에 포함되어 있습니다.

이 결과를 json으로 인코딩해야 set하지만 비어 set있는 json.dumps메소드를 메소드에 전달 하면 a가 발생합니다 TypeError.

  File "/usr/lib/python2.7/json/encoder.py", line 201, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "/usr/lib/python2.7/json/encoder.py", line 264, in iterencode
    return _iterencode(o, 0)
  File "/usr/lib/python2.7/json/encoder.py", line 178, in default
    raise TypeError(repr(o) + " is not JSON serializable")
TypeError: set([]) is not JSON serializable

json.JSONEncoder맞춤 default메소드 가있는 클래스에 대한 확장을 만들 수 있다는 것을 알고 있지만에서 변환을 시작할 위치조차 확실하지 않습니다 set. set기본 메소드 내의 값 으로 사전을 작성한 다음 인코딩을 리턴해야합니까? 이상적으로는 기본 인코더가 원래 인코더가 질식하는 모든 데이터 유형을 처리 할 수 ​​있도록하고 싶습니다 (Mongo를 데이터 소스로 사용하므로 날짜 도이 오류를 발생시키는 것처럼 보입니다)

올바른 방향으로 힌트를 주시면 감사하겠습니다.

편집하다:

답변 해주셔서 감사합니다! 아마도 더 정확했을 것입니다.

나는 set번역되는 것의 한계를 극복하기 위해 여기에 답을 활용하고 상향 조정 했지만 문제가되는 내부 키가 있습니다.

의 객체는로 set번역되는 복잡한 객체 __dict__이지만 json 인코더의 기본 유형에 적합하지 않은 속성 값을 포함 할 수도 있습니다.

이것에는 많은 다른 유형이 set있으며 해시는 기본적으로 엔티티의 고유 ID를 계산하지만 NoSQL의 진정한 정신에는 자식 객체에 무엇이 포함되어 있는지 정확하게 알려주지는 않습니다.

한 객체는에 대한 날짜 값을 포함 starts할 수있는 반면 다른 객체에는 "기본이 아닌"객체를 포함하는 키가없는 다른 스키마가있을 수 있습니다.

그렇기 때문에 내가 생각할 수있는 유일한 솔루션 은 다른 경우를 켜기 JSONEncoder위해 default방법을 대체 하기 위해 확장하는 것이 었습니다 . 중첩 된 객체에서 defaultgo by key 에서 반환 된 값 이 전체 객체를 보는 일반적인 포함 / 삭제입니까? 이 방법은 중첩 값을 어떻게 수용합니까? 이전 질문을 살펴본 결과 사례 별 인코딩에 대한 최선의 접근 방법을 찾지 못하는 것 같습니다 (불행히도 여기서해야 할 일처럼 보입니다).


3
dict? 나는 당신이 list세트 에서 벗어나서 인코더로 전달 하고 싶다고 생각합니다 ... 예 :encode(list(myset))
Constantinius

2
JSON을 사용하는 대신 YAML을 사용할 수 있습니다 (JSON은 기본적으로 YAML의 하위 집합 임).
Paolo Moretti

@PaoloMoretti : 그래도 어떤 이점이 있습니까? 세트가 보편적으로 지원되는 YAML 데이터 유형이라고 생각하지 않으며 특히 API와 관련하여 덜 널리 지원됩니다.

@PaoloMoretti 입력 해 주셔서 감사합니다. 그러나 응용 프로그램 프런트 엔드에는 반환 유형으로 JSON이 필요하며이 요구 사항은 모든 목적을 위해 수정되었습니다.
DeaconDesperado

2
@delnan 나는 세트날짜 를 기본적으로 지원하기 때문에 YAML을 제안 하고 있었다 .
Paolo Moretti

답변:


116

JSON 표기법에는 소수의 기본 데이터 유형 (객체, 배열, 문자열, 숫자, 부울 및 null) 만 있으므로 JSON으로 직렬화 된 항목은 이러한 유형 중 하나로 표현해야합니다.

json 모듈 docs에 표시된 것처럼 이 변환은 JSONEncoderJSONDecoder에 의해 자동으로 수행 될 수 있지만 필요한 다른 구조를 포기하게됩니다 (세트로 목록을 변환하면 정기적으로 복구 할 수있는 기능이 손실 됨) 목록을 사용하여 세트를 사전으로 변환하면 사전 dict.fromkeys(s)을 복구하는 기능이 손실됩니다).

보다 정교한 솔루션은 다른 기본 JSON 유형과 공존 할 수있는 사용자 정의 유형을 빌드하는 것입니다. 이를 통해 목록, 집합, dicts, 소수, 날짜 시간 객체 등을 포함하는 중첩 구조를 저장할 수 있습니다.

from json import dumps, loads, JSONEncoder, JSONDecoder
import pickle

class PythonObjectEncoder(JSONEncoder):
    def default(self, obj):
        if isinstance(obj, (list, dict, str, unicode, int, float, bool, type(None))):
            return JSONEncoder.default(self, obj)
        return {'_python_object': pickle.dumps(obj)}

def as_python_object(dct):
    if '_python_object' in dct:
        return pickle.loads(str(dct['_python_object']))
    return dct

다음은 목록, dicts 및 세트를 처리 할 수 ​​있음을 보여주는 샘플 세션입니다.

>>> data = [1,2,3, set(['knights', 'who', 'say', 'ni']), {'key':'value'}, Decimal('3.14')]

>>> j = dumps(data, cls=PythonObjectEncoder)

>>> loads(j, object_hook=as_python_object)
[1, 2, 3, set(['knights', 'say', 'who', 'ni']), {u'key': u'value'}, Decimal('3.14')]

또는 YAML , Twisted Jelly 또는 Python의 pickle 모듈 과 같은보다 일반적인 용도의 직렬화 기술을 사용하는 것이 유용 할 수 있습니다 . 이들은 각각 훨씬 더 넓은 범위의 데이터 유형을 지원합니다.


11
이것은 내가 YAML이 JSON보다 더 일반적인 목적이라고 들었습니다. o_O
Karl Knechtel

13
@KarlKnechtel YAML은 JSON의 슈퍼 세트입니다 (거의 거의). 또한 이진 데이터, 세트, ​​정렬 된 맵 및 타임 스탬프에 대한 태그를 추가합니다. 더 많은 데이터 유형을 지원하는 것이 "보다 일반적인 목적"의 의미입니다. "일반적인 목적"이라는 문구를 다른 의미로 사용하고있는 것 같습니다.
Raymond Hettinger 2016 년

4
jsonpickle 도 잊지 마십시오 .이 답변에서 알 수 있듯이 Python 객체를 JSON으로 산세하기위한 일반화 된 라이브러리입니다.
Jason R. Coombs

4
1.2 버전부터 YAML은 엄격한 JSON 상위 집합입니다. 모든 유효한 JSON은 이제 유효한 YAML입니다. yaml.org/spec/1.2/spec.html
steveha

2
이 코드 예제는 가져 JSONDecoder오지만 사용하지 않습니다
watsonic

115

당신은을 반환하는 사용자 지정 인코더 만들 수 있습니다 list그것이 발생 때를 set. 예를 들면 다음과 같습니다.

>>> import json
>>> class SetEncoder(json.JSONEncoder):
...    def default(self, obj):
...       if isinstance(obj, set):
...          return list(obj)
...       return json.JSONEncoder.default(self, obj)
... 
>>> json.dumps(set([1,2,3,4,5]), cls=SetEncoder)
'[1, 2, 3, 4, 5]'

이 방법으로 다른 유형도 감지 할 수 있습니다. 목록이 실제로 세트임을 유지해야하는 경우 사용자 정의 인코딩을 사용할 수 있습니다. 같은 return {'type':'set', 'list':list(obj)}것이 효과가있을 수 있습니다.

중첩 유형을 설명하려면 다음을 직렬화하십시오.

>>> class Something(object):
...    pass
>>> json.dumps(set([1,2,3,4,5,Something()]), cls=SetEncoder)

다음과 같은 오류가 발생합니다.

TypeError: <__main__.Something object at 0x1691c50> is not JSON serializable

이것은 인코더가 list반환 된 결과 를 가져 와서 자식의 serializer를 재귀 적으로 호출 함을 나타냅니다 . 여러 유형에 대한 사용자 정의 직렬 변환기를 추가하려면 다음을 수행하십시오.

>>> class SetEncoder(json.JSONEncoder):
...    def default(self, obj):
...       if isinstance(obj, set):
...          return list(obj)
...       if isinstance(obj, Something):
...          return 'CustomSomethingRepresentation'
...       return json.JSONEncoder.default(self, obj)
... 
>>> json.dumps(set([1,2,3,4,5,Something()]), cls=SetEncoder)
'[1, 2, 3, 4, 5, "CustomSomethingRepresentation"]'

고마워, 나는 이것이 내가 필요한 유형이라고 더 잘 지정하기 위해 질문을 편집했습니다. 내가 이해할 수없는 것은이 방법이 중첩 된 객체를 처리하는 방법입니다. 귀하의 예에서 반환 값은 set에 대한 목록이지만 전달 된 객체에 날짜 (또 다른 잘못된 데이터 유형)가 포함 된 세트가 있으면 어떻게됩니까? 기본 방법 자체 내에서 키를 뚫어야합니까? 정말 감사합니다!
DeaconDesperado 1

1
JSON 모듈이 중첩 객체를 처리한다고 생각합니다. 목록을 다시 가져 오면 각 목록을 인코딩하려고 시도하는 목록 항목을 반복합니다. 둘 중 하나가 날짜이면 default함수는 다시 obj날짜 개체 인 함수로 다시 호출 되므로 테스트하고 날짜 표시를 반환하면됩니다.
jterrace

따라서 기본 메소드는 전달 된 하나의 오브젝트에 대해 여러 번 실행될 수 있습니다. "목록에있는"개별 키도 볼 수 있기 때문입니다.
DeaconDesperado

일종의 동일한 객체에 대해 여러 번 호출되지는 않지만 자식으로 재귀 할 수 있습니다. 업데이트 된 답변을 참조하십시오.
jterrace

당신이 설명한대로 정확하게 작동했습니다. 나는 여전히 몇 가지 결함을 알아 내야하지만 대부분 리팩토링 할 수있는 물건 일 것입니다. 안내해 주셔서 감사합니다.
DeaconDesperado

7

나는 적응 레이몬드 Hettinger의 솔루션 파이썬 3를.

변경된 내용은 다음과 같습니다.

  • unicode 사라졌다
  • 부모의 호출 업데이트 defaultsuper()
  • 파이썬 3에서는 JSON으로 변환 할 수 없기 때문에 유형 base64을 직렬화하는 데 사용bytesstrbytes
from decimal import Decimal
from base64 import b64encode, b64decode
from json import dumps, loads, JSONEncoder
import pickle

class PythonObjectEncoder(JSONEncoder):
    def default(self, obj):
        if isinstance(obj, (list, dict, str, int, float, bool, type(None))):
            return super().default(obj)
        return {'_python_object': b64encode(pickle.dumps(obj)).decode('utf-8')}

def as_python_object(dct):
    if '_python_object' in dct:
        return pickle.loads(b64decode(dct['_python_object'].encode('utf-8')))
    return dct

data = [1,2,3, set(['knights', 'who', 'say', 'ni']), {'key':'value'}, Decimal('3.14')]
j = dumps(data, cls=PythonObjectEncoder)
print(loads(j, object_hook=as_python_object))
# prints: [1, 2, 3, {'knights', 'who', 'say', 'ni'}, {'key': 'value'}, Decimal('3.14')]

4
관련 질문에 대한 이 답변 의 끝 부분에 표시된 코드 는 바이트 객체 json.dumps()가 (에서) 반환 및 / 또는 인코딩하여 필요하지 않은 내용을 'latin1'건너 뛰고 [only] 디코딩하여 동일한 작업을 수행합니다 base64.
martineau

6

JSON에서는 사전, 목록 및 기본 객체 유형 (int, string, bool) 만 사용할 수 있습니다.


5
"Primitive Object Type"은 파이썬에 대해 이야기 할 때 의미가 없습니다. "내장 객체"는 더 의미가 있지만 여기에는 너무 광범위합니다 (처음 : 사전, 목록 및 세트도 포함). (JSON 용어는 다를 수 있습니다.)

string number object array true false null
Joseph Le Brech 2016

6

default메소드 를 제공하기 위해 사용자 정의 인코더 클래스를 작성할 필요가 없습니다 . 키워드 인수로 전달 될 수 있습니다.

import json

def serialize_sets(obj):
    if isinstance(obj, set):
        return list(obj)

    return obj

json_str = json.dumps(set([1,2,3]), default=serialize_sets)
print(json_str)

그 결과 [1, 2, 3]지원되는 모든 파이썬 버전이다.


4

일반적인 Python 객체가 아닌 세트 만 인코딩하고 사람이 쉽게 읽을 수 있도록하려면 Raymond Hettinger의 간단한 답변을 사용할 수 있습니다.

import json
import collections

class JSONSetEncoder(json.JSONEncoder):
    """Use with json.dumps to allow Python sets to be encoded to JSON

    Example
    -------

    import json

    data = dict(aset=set([1,2,3]))

    encoded = json.dumps(data, cls=JSONSetEncoder)
    decoded = json.loads(encoded, object_hook=json_as_python_set)
    assert data == decoded     # Should assert successfully

    Any object that is matched by isinstance(obj, collections.Set) will
    be encoded, but the decoded value will always be a normal Python set.

    """

    def default(self, obj):
        if isinstance(obj, collections.Set):
            return dict(_set_object=list(obj))
        else:
            return json.JSONEncoder.default(self, obj)

def json_as_python_set(dct):
    """Decode json {'_set_object': [1,2,3]} to set([1,2,3])

    Example
    -------
    decoded = json.loads(encoded, object_hook=json_as_python_set)

    Also see :class:`JSONSetEncoder`

    """
    if '_set_object' in dct:
        return set(dct['_set_object'])
    return dct

1

빠른 덤프 만 필요하고 사용자 지정 인코더를 구현하지 않으려는 경우 다음을 사용할 수 있습니다.

json_string = json.dumps(data, iterable_as_array=True)

그러면 모든 세트 (및 기타 반복 가능 항목)가 배열로 변환됩니다. json을 구문 분석 할 때 해당 필드가 배열을 유지한다는 점에 유의하십시오. 유형을 유지하려면 사용자 정의 인코더를 작성해야합니다.


7
나는이 때 내가 얻을 : 형식 오류를 : __init를 __ () 예기치 않은 키워드 인수 'iterable_as_array'도착
기압


수입 JSON으로 simplejson 다음 json_string = json.dumps는 (데이터, iterable_as_array이 True =) 파이썬에서 잘 3.6 일
fraverta

1

수용 된 솔루션의 단점 중 하나 는 출력이 매우 파이썬 특정 적이라는 것입니다. 즉 원시 json 출력은 사람이 관찰하거나 다른 언어 (예 : 자바 스크립트)로로드 할 수 없습니다. 예:

db = {
        "a": [ 44, set((4,5,6)) ],
        "b": [ 55, set((4,3,2)) ]
        }

j = dumps(db, cls=PythonObjectEncoder)
print(j)

당신을 얻을 것입니다 :

{"a": [44, {"_python_object": "gANjYnVpbHRpbnMKc2V0CnEAXXEBKEsESwVLBmWFcQJScQMu"}], "b": [55, {"_python_object": "gANjYnVpbHRpbnMKc2V0CnEAXXEBKEsCSwNLBGWFcQJScQMu"}]}

나가는 목록을 포함하는 dict로 세트를 다운 그레이드하고 동일한 인코더를 사용하여 파이썬에로드 할 때 세트로 다시 다운 그레이드하여 관찰력과 언어 불가지론을 보존하는 솔루션을 제안 할 수 있습니다.

from decimal import Decimal
from base64 import b64encode, b64decode
from json import dumps, loads, JSONEncoder
import pickle

class PythonObjectEncoder(JSONEncoder):
    def default(self, obj):
        if isinstance(obj, (list, dict, str, int, float, bool, type(None))):
            return super().default(obj)
        elif isinstance(obj, set):
            return {"__set__": list(obj)}
        return {'_python_object': b64encode(pickle.dumps(obj)).decode('utf-8')}

def as_python_object(dct):
    if '__set__' in dct:
        return set(dct['__set__'])
    elif '_python_object' in dct:
        return pickle.loads(b64decode(dct['_python_object'].encode('utf-8')))
    return dct

db = {
        "a": [ 44, set((4,5,6)) ],
        "b": [ 55, set((4,3,2)) ]
        }

j = dumps(db, cls=PythonObjectEncoder)
print(j)
ob = loads(j)
print(ob["a"])

당신을 얻는다 :

{"a": [44, {"__set__": [4, 5, 6]}], "b": [55, {"__set__": [2, 3, 4]}]}
[44, {'__set__': [4, 5, 6]}]

참고 키로 요소를 가지고 사전을 직렬화하는 것으로 "__set__"이 메커니즘을 깰 것입니다. 그래서 __set__지금은 예약이되었다 dict키를 누릅니다. 분명히 더 난독 화 된 다른 키를 자유롭게 사용하십시오.

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