简体   繁体   English

将json字符串反序列化为python中的对象

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

I have the following string我有以下字符串

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

I Want to deserialize to a object of class我想反序列化为类的对象

class payload
    string action
    string method
    string data

I am using python 2.6 and 2.7我正在使用 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'

To elaborate on Sami's answer:详细说明萨米的回答:

From 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)

My objection to the我的反对意见

.__dict__ 

solution is that while it does the job and is concise, the Payload class becomes totally generic - it doesn't document its fields.解决方案是,虽然它可以完成工作并且简洁,但 Payload 类变得完全通用- 它不记录其字段。

For example, if the Payload message had an unexpected format, instead of throwing a key not found error when the Payload was created, no error would be generated until the payload was used.例如,如果 Payload 消息具有意外格式,那么在创建 Payload 时不会抛出 key not found 错误,而是在使用 Payload 之前不会生成错误。

If you are embracing the type hints in Python 3.6, you can do it like this:如果您正在接受 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

Which then allows you do instantiate typed objects like this:然后允许您像这样实例化类型化对象:

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)

This syntax requires Python 3.6 though and does not cover all cases - for example, support for typing.Any... But at least it does not pollute the classes that need to be deserialized with extra init/tojson methods.虽然这种语法需要 Python 3.6 并且不能涵盖所有情况——例如,支持typing.Any...但至少它不会污染需要用额外的 init/tojson 方法反序列化的类。

If you want to save lines of code and leave the most flexible solution, we can deserialize the json string to a dynamic object:如果你想节省代码行并留下最灵活的解决方案,我们可以将json字符串反序列化为动态对象:

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


>>>> p.action >>>> 动作
output: u'print'输出:你'打印'

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

I thought I lose all my hairs for solving this 'challenge'.我以为我为解决这个“挑战”而失去了所有的头发。 I faced following problems:我遇到了以下问题:

  1. How to deserialize nested objects, lists etc.如何反序列化嵌套对象、列表等。
  2. I like constructors with specified fields我喜欢具有指定字段的构造函数
  3. I don't like dynamic fields我不喜欢动态字段
  4. I don't like hacky solutions我不喜欢hacky的解决方案

I found a library called jsonpickle which is has proven to be really useful.我发现了一个名为jsonpickle的库,它已被证明非常有用。

Installation:安装:

pip install jsonpickle

Here is a code example with writing nested objects to file:这是将嵌套对象写入文件的代码示例:

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)

Output:输出:

John
21
John jr.

Website: http://jsonpickle.github.io/网站: http : //jsonpickle.github.io/

Hope it will save your time (and hairs).希望它能节省您的时间(和头发)。

I prefer to add some checking of the fields, eg so you can catch errors like when you get invalid json, or not the json you were expecting, so I used namedtuples:我更喜欢添加一些字段检查,例如,这样您就可以捕获错误,例如当您获得无效的 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)

this will let give you nice errors when the json you are parsing does not match the thing you want it to parse当您解析的 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'

if you want to parse nested relations, eg '{"parent":{"child":{"name":"henry"}}}' you can still use the namedtuples, and even a more reusable function如果你想解析嵌套关系,例如'{"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

giving you给你

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

In recent versions of python, you can use marshmallow-dataclass :在最新版本的 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"})

You can specialize an encoder for object creation: http://docs.python.org/2/library/json.html您可以专门用于对象创建的编码器: 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)

Another way is to simply pass the json string as a dict to the constructor of your object.另一种方法是简单地将 json 字符串作为 dict 传递给对象的构造函数。 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:下面两行python代码将构造它:

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

Basically, we first create a generic json object from the json string.基本上,我们首先从 json 字符串创建一个通用的 json 对象。 Then, we pass the generic json object as a dict to the constructor of the Payload class.然后,我们将通用 json 对象作为 dict 传递给 Payload 类的构造函数。 The constructor of Payload class interprets the dict as keyword arguments and sets all the appropriate fields. Payload 类的构造函数将 dict 解释为关键字参数并设置所有适当的字段。

There are different methods to deserialize json string to an object.有多种方法可以将 json 字符串反序列化为对象。 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. Pykson,是 Python 的 JSON 序列化器和反序列化器,可以帮助您实现。 Simply define Payload class model as JsonObject then use Pykson to convert json string to object.只需将 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 is an increasingly popular library for python 3.6+ projects. pydantic是一个越来越流行的 Python 3.6+ 项目库。 It mainly does data validation and settings management using type hints.它主要使用类型提示进行数据验证和设置管理。

A basic example using different types:使用不同类型的基本示例:

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)

What I love about the lib is that you get a lot of goodies for free, like我喜欢 lib 的是你可以免费获得很多好东西,比如

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

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. payload.more_info也将被视为payload一个实例,这将导致解析错误。

From the official docs:来自官方文档:

object_hook is an optional function that will be called with the result of any object literal decoded (a dict). object_hook 是一个可选函数,将使用任何对象文字解码的结果(字典)调用。 The return value of object_hook will be used instead of the dict.将使用 object_hook 的返回值而不是 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)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM