Подтвердить что ты не робот

Дессериализация строки json для объекта в python

У меня есть следующая строка

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

Я хочу десериализовать объект класса

class payload
    string action
    string method
    string data

Я использую python 2.6 и 2.7

4b9b3361

Ответ 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'

Ответ 2

Чтобы уточнить ответ Сами:

Из документов:

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 становится полностью общим - он не документирует свои поля.

Например, если сообщение "Полезная нагрузка" имело неожиданный формат, вместо того, чтобы бросать ошибку, не найденную ключом при создании полезной нагрузки, не будет генерироваться ошибка, пока полезная нагрузка не будет использована.

Ответ 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, хотя и не охватывает всех случаев - например, поддержка для ввода. Любой... Но по крайней мере он не загрязняет классы, которые необходимо десериализовать с помощью дополнительных методов init/tojson.

Ответ 4

Если вы хотите сохранить строки кода и оставить наиболее гибкое решение, мы можем десериализовать строку json для динамического объекта:

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


→ → p.action
вывода: u'print '

→ → p.method
output: u'onData '

Ответ 5

Я предпочитаю добавлять некоторую проверку полей, например. поэтому вы можете ловить ошибки, например, когда вы получаете недействительный 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"}}}' вы все равно можете использовать 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'

Ответ 7

Я думал, что потерял все свои волосы для решения этой "проблемы". Я столкнулся со следующими проблемами:

  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/

Надеюсь, это сэкономит ваше время (и волосы).

Ответ 8

Другой способ - просто передать строку json в качестве диктанта конструктору вашего объекта. Например, ваш объект:

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 как аргументы ключевого слова и устанавливает все соответствующие поля.

Ответ 9

Хотя ответ Алекса указывает нам на хороший метод, реализация, которую он дал, наталкивается на проблему, когда у нас есть вложенные объекты.

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 - необязательная функция, которая будет вызываться с результатом декодирования любого литерала объекта (dict). Возвращаемое значение 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)

Ответ 10

В последних версиях 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"})

Ответ 11

Существуют разные методы десериализации строки json для объекта. Все вышеперечисленные методы являются приемлемыми, но я предлагаю использовать библиотеку, чтобы предотвратить дублирование проблем с ключами или сериализацию/десериализацию вложенных объектов.

Pykson, это сериализатор и десериализатор JSON для 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)