Как преобразовать данные JSON в объект Python
Я хочу использовать 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 для удобства использования?
Ответы
Ответ 1
Вы можете сделать это в одной строке, используя 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
параметр.
Ответ 2
Ознакомьтесь с разделом " Специализирование декодирования объектов 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']
Так же, как обычный словарь.
Ответ 3
Это не кодекс гольфа, но вот мой самый короткий трюк, используя types.SimpleNamespace
в качестве контейнера для объектов JSON.
По сравнению с ведущим решением namedtuple
это:
- вероятно, быстрее/меньше, так как он не создает класс для каждого объекта
- короче
- no
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)
Ответ 4
Вы можете попробовать следующее:
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)
Просто создайте новый объект и передайте параметры как карту.
Ответ 5
Здесь быстрая и грязная альтернатива рассола 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()
Ответ 6
Для сложных объектов вы можете использовать JSON Pickle
Библиотека Python для сериализации любого произвольного графа объектов в JSON. Он может взять практически любой объект Python и превратить его в JSON. Кроме того, он может восстановить объект обратно в Python.
Ответ 7
Я написал небольшую (де) сериализующую структуру под названием any2any, которая помогает выполнять сложные преобразования между двумя типами Python.
В вашем случае, я думаю, вы хотите преобразовать из словаря (полученного с помощью json.loads
) в сложный объект response.education ; response.name
с вложенной структурой response.education.id
и т.д....
Так что именно для этой рамки. Документация пока невелика, но, используя any2any.simple.MappingToObject
, вы сможете сделать это очень легко. Пожалуйста, спросите, нужна ли вам помощь.
Ответ 8
Если вы используете 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
требуются подсказки типов для пользовательских типов.
Ответ 9
Если вы используете Python 3. 6+, вы можете использовать marshmallow-dataclass. Вопреки всем решениям, перечисленным выше, он прост и безопасен:
from marshmallow_dataclass import dataclass
@dataclass
class User:
name: str
user, err = User.Schema().load({"name": "Ramirez"})
Ответ 10
Поскольку никто не дал такого ответа, как я, я опубликую его здесь.
Это надежный класс, который может легко конвертировать туда и обратно между json str
и dict
которые я скопировал из своего ответа на другой вопрос:
import json
class PyJSON(object):
def __init__(self, d):
if type(d) is str:
d = json.loads(d)
self.from_dict(d)
def from_dict(self, d):
self.__dict__ = {}
for key, value in d.items():
if type(value) is dict:
value = PyJSON(value)
self.__dict__[key] = value
def to_dict(self):
d = {}
for key, value in self.__dict__.items():
if type(value) is PyJSON:
value = value.to_dict()
d[key] = value
return d
def __repr__(self):
return str(self.to_dict())
def __setitem__(self, key, value):
self.__dict__[key] = value
def __getitem__(self, key):
return self.__dict__[key]
json_str = """... json string ..."""
py_json = PyJSON(json_str)
Ответ 11
Модификация ответа @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.
Ответ 12
Улучшение ловасоа очень хороший ответ.
Если вы используете Python 3. 6+, вы можете использовать:
pip install marshmallow-enum
и
pip install marshmallow-dataclass
Это просто и безопасно.
Вы можете преобразовать свой класс в строку-json и наоборот:
От объекта к струне Json:
from marshmallow_dataclass import dataclass
user = User("Danilo","50","RedBull",15,OrderStatus.CREATED)
user_json = User.Schema().dumps(user)
user_json_str = user_json.data
От струны Джсон до объекта:
json_str = '{"name":"Danilo", "orderId":"50", "productName":"RedBull", "quantity":15, "status":"Created"}'
user, err = User.Schema().loads(json_str)
print(user,flush=True)
Определения классов:
class OrderStatus(Enum):
CREATED = 'Created'
PENDING = 'Pending'
CONFIRMED = 'Confirmed'
FAILED = 'Failed'
@dataclass
class User:
def __init__(self, name, orderId, productName, quantity, status):
self.name = name
self.orderId = orderId
self.productName = productName
self.quantity = quantity
self.status = status
name: str
orderId: str
productName: str
quantity: int
status: OrderStatus
Ответ 13
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
Ответ 14
В поисках решения я наткнулся на этот пост в блоге: 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)
Ответ 15
Развернув немного ответ 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)
Ответ 16
Если вы используете Python 3.6 или новее, вы можете взглянуть на squema - легкий модуль для статически типизированных структур данных. Это делает ваш код легко читаемым, в то же время обеспечивая простую проверку данных, преобразование и сериализацию без дополнительной работы. Вы можете думать об этом как о более сложной и самоуверенной альтернативе именованных кортежей и классов данных. Вот как вы можете это использовать:
from uuid import UUID
from squema import Squema
class FbApiUser(Squema):
id: UUID
age: int
name: str
def save(self):
pass
user = FbApiUser(**json.loads(response))
user.save()
Ответ 17
Используйте json
module (new в Python 2.6) или simplejson
, который почти всегда установлен.