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

나는 파이썬이 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 에서 반환 된 값 이 전체 객체를 보는 일반적인 포함 / 삭제입니까? 이 방법은 중첩 값을 어떻게 수용합니까? 이전 질문을 살펴본 결과 사례 별 인코딩에 대한 최선의 접근 방법을 찾지 못하는 것 같습니다 (불행히도 여기서해야 할 일처럼 보입니다).



답변

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 모듈 과 같은보다 일반적인 용도의 직렬화 기술을 사용하는 것이 유용 할 수 있습니다 . 이들은 각각 훨씬 더 넓은 범위의 데이터 유형을 지원합니다.


답변

당신은을 반환하는 사용자 지정 인코더 만들 수 있습니다 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"]'


답변

나는 적응 레이몬드 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')]


답변

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


답변

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]지원되는 모든 파이썬 버전이다.


답변

일반적인 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


답변

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

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

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