【问题标题】:Deserialize a json string to an object in python将json字符串反序列化为python中的对象
【发布时间】:2013-03-06 19:10:21
【问题描述】:

我有以下字符串

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

我想反序列化为类的对象

class payload
    string action
    string method
    string data

我正在使用 python 2.6 和 2.7

【问题讨论】:

标签: python json


【解决方案1】:
>>> 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'

【讨论】:

  • 如果这对任何人都有帮助,在我看来,唯一正确的解决方案是图书馆。不是定制的序列化。见jsonpickle
  • 对于已有成员的类,不要像这样分配__dict__。如果现有成员不在 json 字符串中,您将丢失它们。如果必须这样做,请使用self.__dict__.update(otherdict)
【解决方案2】:

详细说明萨米的回答:

来自the docs

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 之前不会生成错误。

【讨论】:

  • 我的 2 美分,将 return Payload(dct['action'], dct['method'], dct['data']) 替换为 return Payload(dct.get('action', None), dct.get('method', None), dct.get('data', None)) 以允许某些字段是可选的。
【解决方案3】:

如果您正在接受 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 方法反序列化的类。

【讨论】:

  • 这在 python 3.7 annotations.get(name) 不再返回 GenericMeta 时失败
【解决方案4】:

如果你想节省几行代码,留下最灵活的方案,我们可以将json字符串反序列化为动态对象:

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


>>>> p.action
输出:u'print'

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

【讨论】:

  • 这非常接近,但不处理嵌套的 JSON。 p.__dict__ = json.loads('{"parent":{"child":{"name":"henry"}}}') 仍然需要像字典一样访问孩子。
【解决方案5】:

我以为我为解决这个“挑战”而失去了所有的头发。我遇到了以下问题:

  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/

希望它能节省您的时间(和头发)。

【讨论】:

  • 是否支持验证?还输入提示?
【解决方案6】:

在最近的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"})

【讨论】:

  • 这似乎是最方便的方式。
  • 谢谢!这正是我想要的
【解决方案7】:

我更喜欢添加一些字段检查,例如所以你可以捕捉到错误,比如当你得到无效的 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'

【讨论】:

    【解决方案8】:

    您可以专门为对象创建编码器: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)
    

    【讨论】:

    • 请不要只将链接粘贴到答案上,因为它随时可能会关闭。也从这里的链接给出简单的答案。
    • 这个问题是关于解码而不是编码,所以这根本没有帮助
    【解决方案9】:

    另一种方法是简单地将 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 解释为关键字参数并设置所有适当的字段。

    【讨论】:

      【解决方案10】:

      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}
      

      【讨论】:

        【解决方案11】:

        虽然Alex's 的回答为我们指出了一种很好的技术,但当我们有嵌套对象时,他给出的实现会遇到问题。

        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)
        
        

        【讨论】:

          【解决方案12】:

          有多种方法可以将 json 字符串反序列化为对象。以上所有方法都可以接受,但我建议使用库来防止重复键问题或嵌套对象的序列化/反序列化。

          Pykson, is a JSON Serializer and Deserializer for Python可以帮你实现。只需将 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)
          

          【讨论】:

            猜你喜欢
            • 2016-06-20
            • 1970-01-01
            • 2014-01-04
            • 2020-02-03
            • 1970-01-01
            • 2012-04-24
            • 1970-01-01
            • 1970-01-01
            • 2016-03-06
            相关资源
            最近更新 更多