簡體   English   中英

將json字符串反序列化為python中的對象

[英]Deserialize a json string to an object in python

我有以下字符串

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

我想反序列化為類的對象

class payload
    string action
    string method
    string data

我正在使用 python 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'

詳細說明薩米的回答:

文檔

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 類變得完全通用- 它不記錄其字段。

例如,如果 Payload 消息具有意外格式,那么在創建 Payload 時不會拋出 key not found 錯誤,而是在使用 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 並且不能涵蓋所有情況——例如,支持typing.Any...但至少它不會污染需要用額外的 init/tojson 方法反序列化的類。

如果你想節省代碼行並留下最靈活的解決方案,我們可以將json字符串反序列化為動態對象:

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


>>>> 動作
輸出:你'打印'

>>>> p.方法
輸出:u'onData'

我以為我為解決這個“挑戰”而失去了所有的頭發。 我遇到了以下問題:

  1. 如何反序列化嵌套對象、列表等。
  2. 我喜歡具有指定字段的構造函數
  3. 我不喜歡動態字段
  4. 我不喜歡hacky的解決方案

我發現了一個名為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/

希望它能節省您的時間(和頭發)。

我更喜歡添加一些字段檢查,例如,這樣您就可以捕獲錯誤,例如當您獲得無效的 json 或不是您期望的 json 時,所以我使用了命名元組:

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"}}}'你仍然可以使用 namedtuples,甚至是一個更可重用的函數

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'

在最新版本的 python 中,您可以使用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"})

您可以專門用於對象創建的編碼器: http : //docs.python.org/2/library/json.html

import json
class ComplexEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, complex):
            return {"real": obj.real,
            "imag": obj.imag,
            "__class__": "complex"}
        return json.JSONEncoder.default(self, obj)

print json.dumps(2 + 1j, cls=ComplexEncoder)

另一種方法是簡單地將 json 字符串作為 dict 傳遞給對象的構造函數。 例如你的對象是:

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

下面兩行python代碼將構造它:

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

基本上,我們首先從 json 字符串創建一個通用的 json 對象。 然后,我們將通用 json 對象作為 dict 傳遞給 Payload 類的構造函數。 Payload 類的構造函數將 dict 解釋為關鍵字參數並設置所有適當的字段。

有多種方法可以將 json 字符串反序列化為對象。 以上所有方法都是可以接受的,但我建議使用庫來防止重復的關鍵問題或嵌套對象的序列化/反序列化。

Pykson,是 Python 的 JSON 序列化器和反序列化器,可以幫助您實現。 只需將 Payload 類模型定義為 JsonObject,然后使用 Pykson 將 json 字符串轉換為對象。

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)

pydantic是一個越來越流行的 Python 3.6+ 項目庫。 它主要使用類型提示進行數據驗證和設置管理。

使用不同類型的基本示例:

from pydantic import BaseModel

class ClassicBar(BaseModel):
    count_drinks: int
    is_open: bool
 
data = {'count_drinks': '226', 'is_open': 'False'}
cb = ClassicBar(**data)
>>> cb
ClassicBar(count_drinks=226, is_open=False)

我喜歡 lib 的是你可以免費獲得很多好東西,比如

>>> cb.json()
'{"count_drinks": 226, "is_open": false}'
>>> cb.dict()
{'count_drinks': 226, 'is_open': False}

雖然亞歷克斯的回答為我們指出了一個很好的技術,但當我們有嵌套對象時,他給出的實現會遇到問題。

class more_info
    string status

class payload
    string action
    string method
    string data
    class more_info

使用以下代碼:

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也將被視為payload一個實例,這將導致解析錯誤。

來自官方文檔:

object_hook 是一個可選函數,將使用任何對象文字解碼的結果(字典)調用。 將使用 object_hook 的返回值而不是 dict。

因此,我更願意提出以下解決方案:

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)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM