Как преобразовать данные 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 объект для легкого использования?
11 ответов:
вы можете сделать это в одну строку, используя
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' srenameпараметр.
проверьте раздел специализированное декодирование объектов 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)
вы можете попробовать это:
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 pickle
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.
Я написал небольшую (de)структуру сериализации под названием any2any это помогает выполнять сложные преобразования между двумя типами Python.
в вашем случае, я думаю, вы хотите преобразовать из словаря (получены с
json.loads) к сложному объектуresponse.education ; response.name, с вложенной структуройresponse.education.idи т. д... Так что это именно то, для чего эта структура создана. Документация еще не велика, но с помощьюany2any.simple.MappingToObject, вы должны быть в состоянии сделать это очень легко. Пожалуйста спросите, нужна ли вам помощь.
изменение @ 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.
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)
Edit
С некоторыми более исследований я нашел способ обобщить без необходимости суперкласс Регистрация вызова метода, с помощью метакласс
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
в поисках решения, я наткнулся на это сообщение в блоге: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)
использовать
jsonмодуль (новое в Python 2.6) илиsimplejsonмодуль, который почти всегда установлен.
Comments