У меня есть следующая строка
{"action":"print","method":"onData","data":"Madan Mohan"}
Я хочу десериализовать объект класса
class payload
string action
string method
string data
Я использую python 2.6 и 2.7
У меня есть следующая строка
{"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 становится полностью общим - он не документирует свои поля.
Например, если сообщение "Полезная нагрузка" имело неожиданный формат, вместо того, чтобы бросать ошибку, не найденную ключом при создании полезной нагрузки, не будет генерироваться ошибка, пока полезная нагрузка не будет использована.
Если вы используете подсказки типа в 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.
Если вы хотите сохранить строки кода и оставить наиболее гибкое решение, мы можем десериализовать строку json для динамического объекта:
p = lambda:None
p.__dict__ = json.loads('{"action": "print", "method": "onData", "data": "Madan Mohan"}')
→ → p.action
вывода: u'print '
→ → p.method
output: 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"}}}'
вы все равно можете использовать 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'
Вы можете специализировать кодировщик для создания объекта: http://docs.python.org/2/library/json.html
Я думал, что потерял все свои волосы для решения этой "проблемы". Я столкнулся со следующими проблемами:
Я нашел библиотеку под названием 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 в качестве диктанта конструктору вашего объекта. Например, ваш объект:
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 как аргументы ключевого слова и устанавливает все соответствующие поля.
Хотя ответ Алекса указывает нам на хороший метод, реализация, которую он дал, наталкивается на проблему, когда у нас есть вложенные объекты.
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)
В последних версиях 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"})
Существуют разные методы десериализации строки 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)