development

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

big-blog 2020. 6. 27. 10:11
반응형

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]'

You can detect other types this way too. If you need to retain that the list was actually a set, you could use a custom encoding. Something like return {'type':'set', 'list':list(obj)} might work.

To illustrated nested types, consider serializing this:

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

This raises the following error:

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

This indicates that the encoder will take the list result returned and recursively call the serializer on its children. To add a custom serializer for multiple types, you can do this:

>>> 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"]'

I adapted Raymond Hettinger's solution to python 3.

Here is what has changed:

  • unicode disappeared
  • updated the call to the parents' default with super()
  • using base64 to serialize the bytes type into str (because it seems that bytes in python 3 can't be converted to JSON)
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')]

Only dictionaries, Lists and primitive object types (int, string, bool) are available in JSON.


If you only need to encode sets, not general Python objects, and want to keep it easily human-readable, a simplified version of Raymond Hettinger's answer can be used:

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

If you need just quick dump and don't want to implement custom encoder. You can use the following:

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

This will convert all sets (and other iterables) into arrays. Just beware that those fields will stay arrays when you parse the json back. If you want to preserve the types, you need to write custom encoder.

참고URL : https://stackoverflow.com/questions/8230315/how-to-json-serialize-sets

반응형