IT Share you

파이썬에서 json 문자열을 객체로 역 직렬화

shareyou 2020. 12. 9. 21:59
반응형

파이썬에서 json 문자열을 객체로 역 직렬화


다음 문자열이 있습니다.

{"action":"print","method":"onData","data":"Madan Mohan"}

클래스 개체로 역 직렬화하고 싶습니다.

class payload
    string action
    string method
    string data

파이썬 2.6과 2.7을 사용하고 있습니다


>>> j = '{"action": "print", "method": "onData", "data": "Madan Mohan"}'
>>> import json
>>> 
>>> class Payload(object):
...     def __init__(self, j):
...         self.__dict__ = json.loads(j)
... 
>>> p = Payload(j)
>>>
>>> p.action
'print'
>>> p.method
'onData'
>>> p.data
'Madan Mohan'

Sami의 답변에 대해 자세히 설명하려면 :

에서 워드 프로세서 :

class Payload(object):
    def __init__(self, action, method, data):
        self.action = action
        self.method = method
        self.data = data

import json

def as_payload(dct):
    return Payload(dct['action'], dct['method'], dct['data'])

payload = json.loads(message, object_hook = as_payload)

에 대한 나의 이의

.__dict__ 

솔루션은 작업을 수행하고 간결하지만 Payload 클래스는 완전히 일반화 되어 필드를 문서화하지 않습니다.

예를 들어, 페이로드 메시지에 예기치 않은 형식이있는 경우 페이로드가 생성 될 때 키를 찾을 수 없음 오류가 발생하는 대신 페이로드가 사용될 때까지 오류가 생성되지 않습니다.


Python 3.6에서 유형 힌트를 수용하는 경우 다음과 같이 할 수 있습니다.

def from_json(data, cls):
    annotations: dict = cls.__annotations__ if hasattr(cls, '__annotations__') else None
    if issubclass(cls, List):
        list_type = cls.__args__[0]
        instance: list = list()
        for value in data:
            instance.append(from_json(value, list_type))
        return instance
    elif issubclass(cls, Dict):
            key_type = cls.__args__[0]
            val_type = cls.__args__[1]
            instance: dict = dict()
            for key, value in data.items():
                instance.update(from_json(key, key_type), from_json(value, val_type))
            return instance
    else:
        instance : cls = cls()
        for name, value in data.items():
            field_type = annotations.get(name)
            if inspect.isclass(field_type) and isinstance(value, (dict, tuple, list, set, frozenset)):
                setattr(instance, name, from_json(value, field_type))
            else:
                setattr(instance, name, value)
        return instance

그러면 다음과 같이 유형이 지정된 객체를 인스턴스화 할 수 있습니다.

class Bar:
    value : int

class Foo:
    x : int
    bar : List[Bar]


obj : Foo = from_json(json.loads('{"x": 123, "bar":[{"value": 3}, {"value": 2}, {"value": 1}]}'), Foo)
print(obj.x)
print(obj.bar[2].value)

이 구문은 Python 3.6을 필요로하며 모든 경우를 포함하지는 않습니다-예를 들어, 타이핑 지원 .Any ... 그러나 최소한 추가 init / tojson 메소드로 역 직렬화해야하는 클래스를 오염 시키지는 않습니다.


코드 줄을 저장하고 가장 유연한 솔루션을 유지하려면 json 문자열을 동적 객체로 역 직렬화 할 수 있습니다.

p = lambda:None
p.__dict__ = json.loads('{"action": "print", "method": "onData", "data": "Madan Mohan"}')


>>>> p.action
출력 : u'print '

>>>> p.method
출력 : u'onData '


예를 들어 잘못된 json을 얻거나 예상했던 json이 아닌 경우와 같은 오류를 포착 할 수 있도록 필드 검사를 추가하는 것을 선호하므로 namedtuples를 사용했습니다.

from collections import namedtuple
payload = namedtuple('payload', ['action', 'method', 'data'])
def deserialize_payload(json):
    kwargs =  dict([(field, json[field]) for field in payload._fields]) 
    return payload(**kwargs)

이것은 당신이 파싱하는 json이 파싱하려는 것과 일치하지 않을 때 좋은 오류를 줄 것입니다.

>>> json = {"action":"print","method":"onData","data":"Madan Mohan"}
>>> deserialize_payload(json)
payload(action='print', method='onData', data='Madan Mohan')
>>> badjson = {"error":"404","info":"page not found"}
>>> deserialize_payload(badjson)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in deserialize_payload
KeyError: 'action'

중첩 된 관계를 구문 분석하려는 경우, 예 '{"parent":{"child":{"name":"henry"}}}'들어 명명 된 튜플을 계속 사용할 수 있으며 더 많은 재사용 가능한 함수를 사용할 수 있습니다.

Person = namedtuple("Person", ['parent'])
Parent = namedtuple("Parent", ['child'])
Child = namedtuple('Child', ['name'])
def deserialize_json_to_namedtuple(json, namedtuple):
    return namedtuple(**dict([(field, json[field]) for field in namedtuple._fields]))

def deserialize_person(json):
     json['parent']['child']  = deserialize_json_to_namedtuple(json['parent']['child'], Child)
     json['parent'] =  deserialize_json_to_namedtuple(json['parent'], Parent) 
     person = deserialize_json_to_namedtuple(json, Person)
     return person

당신에게주는

>>> deserialize_person({"parent":{"child":{"name":"henry"}}})
Person(parent=Parent(child=Child(name='henry')))
>>> deserialize_person({"error":"404","info":"page not found"})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in deserialize_person
KeyError: 'parent'

객체 생성을위한 인코더를 전문화 할 수 있습니다. http://docs.python.org/2/library/json.html


이 '도전'을 풀기 위해 머리카락을 다 잃은 줄 알았다. 다음과 같은 문제에 직면했습니다.

  1. 중첩 된 개체, 목록 등을 역 직렬화하는 방법
  2. 지정된 필드가있는 생성자를 좋아합니다.
  3. 동적 필드가 마음에 들지 않습니다.
  4. 나는 해키 솔루션을 좋아하지 않는다

jsonpickle정말 유용한 것으로 입증 된 라이브러리를 찾았습니다 .

설치:

pip install jsonpickle

다음은 중첩 된 객체를 파일에 쓰는 코드 예제입니다.

import jsonpickle


class SubObject:
    def __init__(self, sub_name, sub_age):
        self.sub_name = sub_name
        self.sub_age = sub_age


class TestClass:

    def __init__(self, name, age, sub_object):
        self.name = name
        self.age = age
        self.sub_object = sub_object


john_junior = SubObject("John jr.", 2)

john = TestClass("John", 21, john_junior)

file_name = 'JohnWithSon' + '.json'

john_string = jsonpickle.encode(john)

with open(file_name, 'w') as fp:
    fp.write(john_string)

john_from_file = open(file_name).read()

test_class_2 = jsonpickle.decode(john_from_file)

print(test_class_2.name)
print(test_class_2.age)
print(test_class_2.sub_object.sub_name)

산출:

John
21
John jr.

웹 사이트 : http://jsonpickle.github.io/

시간과 머리카락을 절약 할 수 있기를 바랍니다.


Another way is to simply pass the json string as a dict to the constructor of your object. For example your object is:

class Payload(object):
    def __init__(self, action, method, data, *args, **kwargs):
        self.action = action
        self.method = method
        self.data = data

And the following two lines of python code will construct it:

j = json.loads(yourJsonString)
payload = Payload(**j)

Basically, we first create a generic json object from the json string. Then, we pass the generic json object as a dict to the constructor of the Payload class. The constructor of Payload class interprets the dict as keyword arguments and sets all the appropriate fields.


In recent versions of python, you can use marshmallow-dataclass:

from marshmallow_dataclass import dataclass

@dataclass
class Payload
    action:str
    method:str
    data:str

Payload.Schema().load({"action":"print","method":"onData","data":"Madan Mohan"})

While Alex's answer points us to a good technique, the implementation that he gave runs into a problem when we have nested objects.

class more_info
    string status

class payload
    string action
    string method
    string data
    class more_info

with the below code:

def as_more_info(dct):
    return MoreInfo(dct['status'])

def as_payload(dct):
    return Payload(dct['action'], dct['method'], dct['data'], as_more_info(dct['more_info']))

payload = json.loads(message, object_hook = as_payload)

payload.more_info will also be treated as an instance of payload which will lead to parsing errors.

From the official docs:

object_hook is an optional function that will be called with the result of any object literal decoded (a dict). The return value of object_hook will be used instead of the dict.

Hence, I would prefer to propose the following solution instead:

class MoreInfo(object):
    def __init__(self, status):
        self.status = status

    @staticmethod
    def fromJson(mapping):
        if mapping is None:
            return None

        return MoreInfo(
            mapping.get('status')
        )

class Payload(object):
    def __init__(self, action, method, data, more_info):
        self.action = action
        self.method = method
        self.data = data
        self.more_info = more_info

    @staticmethod
    def fromJson(mapping):
        if mapping is None:
            return None

        return Payload(
            mapping.get('action'),
            mapping.get('method'),
            mapping.get('data'),
            MoreInfo.fromJson(mapping.get('more_info'))
        )

import json
def toJson(obj, **kwargs):
    return json.dumps(obj, default=lambda j: j.__dict__, **kwargs)

def fromJson(msg, cls, **kwargs):
    return cls.fromJson(json.loads(msg, **kwargs))

info = MoreInfo('ok')
payload = Payload('print', 'onData', 'better_solution', info)
pl_json = toJson(payload)
l1 = fromJson(pl_json, Payload)


There are different methods to deserialize json string to an object. All above methods are acceptable but I suggest using a library to prevent duplicate key issues or serializing/deserializing of nested objects.

Pykson, is a JSON Serializer and Deserializer for Python which can help you achieve. Simply define Payload class model as JsonObject then use Pykson to convert json string to object.

from pykson import Pykson, JsonObject, StringField

class Payload(pykson.JsonObject):
    action = StringField()
    method = StringField()
    data = StringField()

json_text = '{"action":"print","method":"onData","data":"Madan Mohan"}'
payload = Pykson.from_json(json_text, Payload)

참고URL : https://stackoverflow.com/questions/15476983/deserialize-a-json-string-to-an-object-in-python

반응형