Я хочу использовать Python для преобразования данных JSON в объект Python.
Я получаю объекты данных JSON из API Facebook, которые хочу сохранить в моей базе данных.
Мой текущий вид в Django (Python) (request.POST
содержит JSON):
response = request.POST
user = FbApiUser(user_id = response['id'])
user.name = response['name']
user.username = response['username']
user.save()
Это прекрасно работает, но как мне обрабатывать сложные объекты данных JSON?
Не было бы намного лучше, если бы я мог каким-то образом преобразовать этот объект JSON в объект Python для удобства использования?
Вы можете сделать это в одной строке, используя namedtuple
и object_hook
:
import json
from collections import namedtuple
data = '{"name": "John Smith", "hometown": {"name": "New York", "id": 123}}'
# Parse JSON into an object with attributes corresponding to dict keys.
x = json.loads(data, object_hook=lambda d: namedtuple('X', d.keys())(*d.values()))
print x.name, x.hometown.name, x.hometown.id
или, чтобы повторно использовать это легко:
def _json_object_hook(d): return namedtuple('X', d.keys())(*d.values())
def json2obj(data): return json.loads(data, object_hook=_json_object_hook)
x = json2obj(data)
Если вы хотите, чтобы он обрабатывал ключи, которые не являются хорошими именами атрибутов, проверьте namedtuple
rename
параметр.
Ознакомьтесь с разделом " Специализирование декодирования объектов JSON" в документации по модулю json
. Вы можете использовать это для декодирования объекта JSON в определенный тип Python.
Вот пример:
class User(object):
def __init__(self, name, username):
self.name = name
self.username = username
import json
def object_decoder(obj):
if '__type__' in obj and obj['__type__'] == 'User':
return User(obj['name'], obj['username'])
return obj
json.loads('{"__type__": "User", "name": "John Smith", "username": "jsmith"}',
object_hook=object_decoder)
print type(User) # -> <type 'type'>
Обновить
Если вы хотите получить доступ к данным в словаре через модуль json, сделайте это:
user = json.loads('{"__type__": "User", "name": "John Smith", "username": "jsmith"}')
print user['name']
print user['username']
Так же, как обычный словарь.
Это не кодекс гольфа, но вот мой самый короткий трюк, используя types.SimpleNamespace
в качестве контейнера для объектов JSON.
По сравнению с ведущим решением namedtuple
это:
rename
и, возможно, такое же ограничение на ключи, которые не являются допустимыми идентификаторами (использует setattr
под обложками)Пример:
from __future__ import print_function
import json
try:
from types import SimpleNamespace as Namespace
except ImportError:
# Python 2.x fallback
from argparse import Namespace
data = '{"name": "John Smith", "hometown": {"name": "New York", "id": 123}}'
x = json.loads(data, object_hook=lambda d: Namespace(**d))
print (x.name, x.hometown.name, x.hometown.id)
@post_load
. marshmallow.readthedocs.io/en/latest/...
from types import SimpleNamespace
и используйте: x = json.loads(data, object_hook=lambda d: SimpleNamespace(**d))
Вы можете попробовать следующее:
class User(object):
def __init__(self, name, username, *args, **kwargs):
self.name = name
self.username = username
import json
j = json.loads(your_json)
u = User(**j)
Просто создайте новый объект и передайте параметры как карту.
Здесь быстрая и грязная альтернатива рассола json
import json
class User:
def __init__(self, name, username):
self.name = name
self.username = username
def to_json(self):
return json.dumps(self.__dict__)
@classmethod
def from_json(cls, json_str):
json_dict = json.loads(json_str)
return cls(**json_dict)
# example usage
User("tbrown", "Tom Brown").to_json()
User.from_json(User("tbrown", "Tom Brown").to_json()).to_json()
Для сложных объектов вы можете использовать JSON Pickle
Библиотека Python для сериализации любого произвольного графа объектов в JSON. Он может взять практически любой объект Python и превратить его в JSON. Кроме того, он может восстановить объект обратно в Python.
jsonstruct originally a fork of jsonpickle (Thanks guys!). The key difference between this library and jsonpickle is that during deserialization, jsonpickle requires Python types to be recorded as part of the JSON. This library intends to remove this requirement, instead, requires a class to be passed in as an argument so that its definition can be inspected. It will then return an instance of the given class. This approach is similar to how Jackson (of Java) works.
'[{"name":"object1"},{"name":"object2"}]'
. Jsonpickle тоже не очень хорошо с этим справляется.
Я написал небольшую (де) сериализующую структуру под названием any2any, которая помогает выполнять сложные преобразования между двумя типами Python.
В вашем случае, я думаю, вы хотите преобразовать из словаря (полученного с помощью json.loads
) в сложный объект response.education ; response.name
с вложенной структурой response.education.id
и т.д....
Так что именно для этой рамки. Документация пока невелика, но, используя any2any.simple.MappingToObject
, вы сможете сделать это очень легко. Пожалуйста, спросите, нужна ли вам помощь.
Если вы используете Python 3. 5+, вы можете использовать jsons
для сериализации и десериализации простых старых объектов Python:
import jsons
response = request.POST
# You'll need your class attributes to match your dict keys, so in your case do:
response['id'] = response.pop('user_id')
# Then you can load that dict into your class:
user = jsons.load(response, FbApiUser)
user.save()
Вы также можете сделать FbApiUser
наследовать от jsons.JsonSerializable
для большей элегантности:
user = FbApiUser.from_json(response)
Эти примеры будут работать, если ваш класс состоит из типов Python по умолчанию, таких как строки, целые числа, списки, даты и т.д. Для jsons
требуются подсказки типов для пользовательских типов.
Модификация ответа @DS немного, для загрузки из файла:
def _json_object_hook(d): return namedtuple('X', d.keys())(*d.values())
def load_data(file_name):
with open(file_name, 'r') as file_data:
return file_data.read().replace('\n', '')
def json2obj(file_name): return json.loads(load_data(file_name), object_hook=_json_object_hook)
Одно: это не может загружать элементы с числами впереди. Вот так:
{
"1_first_item": {
"A": "1",
"B": "2"
}
}
Потому что "1_first_item" не является допустимым именем поля python.
В поисках решения я наткнулся на этот пост в блоге: https://blog.mosthege.net/2016/11/12/json-deserialization-of-nested-objects/
Он использует ту же технику, что и в предыдущих ответах, но с использованием декораторов. Еще одна вещь, которая мне показалась полезной, это то, что она возвращает типизированный объект в конце десериализации
class JsonConvert(object):
class_mappings = {}
@classmethod
def class_mapper(cls, d):
for keys, cls in clsself.mappings.items():
if keys.issuperset(d.keys()): # are all required arguments present?
return cls(**d)
else:
# Raise exception instead of silently returning None
raise ValueError('Unable to find a matching class for object: {!s}'.format(d))
@classmethod
def complex_handler(cls, Obj):
if hasattr(Obj, '__dict__'):
return Obj.__dict__
else:
raise TypeError('Object of type %s with value of %s is not JSON serializable' % (type(Obj), repr(Obj)))
@classmethod
def register(cls, claz):
clsself.mappings[frozenset(tuple([attr for attr,val in cls().__dict__.items()]))] = cls
return cls
@classmethod
def to_json(cls, obj):
return json.dumps(obj.__dict__, default=cls.complex_handler, indent=4)
@classmethod
def from_json(cls, json_str):
return json.loads(json_str, object_hook=cls.class_mapper)
Использование:
@JsonConvert.register
class Employee(object):
def __init__(self, Name:int=None, Age:int=None):
self.Name = Name
self.Age = Age
return
@JsonConvert.register
class Company(object):
def __init__(self, Name:str="", Employees:[Employee]=None):
self.Name = Name
self.Employees = [] if Employees is None else Employees
return
company = Company("Contonso")
company.Employees.append(Employee("Werner", 38))
company.Employees.append(Employee("Mary"))
as_json = JsonConvert.to_json(company)
from_json = JsonConvert.from_json(as_json)
as_json_from_json = JsonConvert.to_json(from_json)
assert(as_json_from_json == as_json)
print(as_json_from_json)
Python3.x
Наилучшим подходом, которого я мог достичь с помощью моих знаний, было это.
Обратите внимание, что этот код также обрабатывает set().
Этот подход является общим, просто нуждающимся в расширении класса (во втором примере).
Обратите внимание, что я просто делаю это с файлами, но легко изменить поведение на свой вкус.
Однако это кодек.
Приложив немного больше работы, вы можете построить свой класс другими способами. Я предполагаю, что конструктор по умолчанию его создает, а затем обновляю класс dict.
import json
import collections
class JsonClassSerializable(json.JSONEncoder):
REGISTERED_CLASS = {}
def register(ctype):
JsonClassSerializable.REGISTERED_CLASS[ctype.__name__] = ctype
def default(self, obj):
if isinstance(obj, collections.Set):
return dict(_set_object=list(obj))
if isinstance(obj, JsonClassSerializable):
jclass = {}
jclass["name"] = type(obj).__name__
jclass["dict"] = obj.__dict__
return dict(_class_object=jclass)
else:
return json.JSONEncoder.default(self, obj)
def json_to_class(self, dct):
if '_set_object' in dct:
return set(dct['_set_object'])
elif '_class_object' in dct:
cclass = dct['_class_object']
cclass_name = cclass["name"]
if cclass_name not in self.REGISTERED_CLASS:
raise RuntimeError(
"Class {} not registered in JSON Parser"
.format(cclass["name"])
)
instance = self.REGISTERED_CLASS[cclass_name]()
instance.__dict__ = cclass["dict"]
return instance
return dct
def encode_(self, file):
with open(file, 'w') as outfile:
json.dump(
self.__dict__, outfile,
cls=JsonClassSerializable,
indent=4,
sort_keys=True
)
def decode_(self, file):
try:
with open(file, 'r') as infile:
self.__dict__ = json.load(
infile,
object_hook=self.json_to_class
)
except FileNotFoundError:
print("Persistence load failed "
"'{}' do not exists".format(file)
)
class C(JsonClassSerializable):
def __init__(self):
self.mill = "s"
JsonClassSerializable.register(C)
class B(JsonClassSerializable):
def __init__(self):
self.a = 1230
self.c = C()
JsonClassSerializable.register(B)
class A(JsonClassSerializable):
def __init__(self):
self.a = 1
self.b = {1, 2}
self.c = B()
JsonClassSerializable.register(A)
A().encode_("test")
b = A()
b.decode_("test")
print(b.a)
print(b.b)
print(b.c.a)
редактировать
Проведя дополнительные исследования, я нашел способ обобщения без необходимости вызова метода регистра SUPERCLASS с использованием метакласса.
import json
import collections
REGISTERED_CLASS = {}
class MetaSerializable(type):
def __call__(cls, *args, **kwargs):
if cls.__name__ not in REGISTERED_CLASS:
REGISTERED_CLASS[cls.__name__] = cls
return super(MetaSerializable, cls).__call__(*args, **kwargs)
class JsonClassSerializable(json.JSONEncoder, metaclass=MetaSerializable):
def default(self, obj):
if isinstance(obj, collections.Set):
return dict(_set_object=list(obj))
if isinstance(obj, JsonClassSerializable):
jclass = {}
jclass["name"] = type(obj).__name__
jclass["dict"] = obj.__dict__
return dict(_class_object=jclass)
else:
return json.JSONEncoder.default(self, obj)
def json_to_class(self, dct):
if '_set_object' in dct:
return set(dct['_set_object'])
elif '_class_object' in dct:
cclass = dct['_class_object']
cclass_name = cclass["name"]
if cclass_name not in REGISTERED_CLASS:
raise RuntimeError(
"Class {} not registered in JSON Parser"
.format(cclass["name"])
)
instance = REGISTERED_CLASS[cclass_name]()
instance.__dict__ = cclass["dict"]
return instance
return dct
def encode_(self, file):
with open(file, 'w') as outfile:
json.dump(
self.__dict__, outfile,
cls=JsonClassSerializable,
indent=4,
sort_keys=True
)
def decode_(self, file):
try:
with open(file, 'r') as infile:
self.__dict__ = json.load(
infile,
object_hook=self.json_to_class
)
except FileNotFoundError:
print("Persistence load failed "
"'{}' do not exists".format(file)
)
class C(JsonClassSerializable):
def __init__(self):
self.mill = "s"
class B(JsonClassSerializable):
def __init__(self):
self.a = 1230
self.c = C()
class A(JsonClassSerializable):
def __init__(self):
self.a = 1
self.b = {1, 2}
self.c = B()
A().encode_("test")
b = A()
b.decode_("test")
print(b.a)
# 1
print(b.b)
# {1, 2}
print(b.c.a)
# 1230
print(b.c.c.mill)
# s
Развернув немного ответ DS, если вам нужно, чтобы объект был изменяемым (который не называется namedtuple), вы можете использовать библиотеку recordclass вместо namedtuple:
import json
from recordclass import recordclass
data = '{"name": "John Smith", "hometown": {"name": "New York", "id": 123}}'
# Parse into a mutable object
x = json.loads(data, object_hook=lambda d: recordclass('X', d.keys())(*d.values()))
Модифицированный объект может быть легко преобразован обратно в json с помощью simplejson:
x.name = "John Doe"
new_json = simplejson.dumps(x)
Используйте json
module (new в Python 2.6) или simplejson
, который почти всегда установлен.