Cómo convertir datos JSON en un objeto Python

Quiero usar Python para convertir datos JSON en un objeto Python.

Recibo objetos de datos JSON de la API de Facebook, que quiero almacenar en mi base de datos.

Mi vista actual en Django (Python) ( request.POST contiene el JSON):

 response = request.POST user = FbApiUser(user_id = response['id']) user.name = response['name'] user.username = response['username'] user.save() 

Puedes hacerlo en una línea, usando namedtuple y 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 

o, para reutilizar esto fácilmente:

 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) 

Si desea que maneje claves que no sean buenos nombres de atributos, verifique el parámetro de rename namedtuple .

Consulte la sección titulada Especialización de desencoding de objetos JSON en la documentación del módulo json . Puede usar eso para decodificar un objeto JSON en un tipo de Python específico.

Aquí hay un ejemplo:

 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) # ->  

Actualizar

Si desea acceder a los datos en un diccionario a través del módulo json, haga lo siguiente:

 user = json.loads('{"__type__": "User", "name": "John Smith", "username": "jsmith"}') print user['name'] print user['username'] 

Al igual que un diccionario regular.

Este no es el código de golf, pero aquí está mi truco más corto, usando types.SimpleNamespace como el contenedor para objetos JSON.

En comparación con la solución líder de namedtuple , es:

  • probablemente más rápido / más pequeño, ya que no crea una clase para cada objeto
  • más corta
  • no hay opción de rename , y probablemente la misma limitación en las claves que no son identificadores válidos (usa setattr debajo de las portadas)

Ejemplo:

 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) 

Podrías probar esto:

 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) 

Simplemente crea un nuevo Objeto y pasa los parámetros como un mapa.

Aquí hay una alternativa rápida y sucia de 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() 

Para objetos complejos, puedes usar JSON Pickle

Biblioteca de Python para serializar cualquier gráfico de objeto arbitrario en JSON. Puede tomar casi cualquier objeto de Python y convertir el objeto en JSON. Además, puede reconstituir el objeto de nuevo en Python.

He escrito un pequeño marco de (des) serialización llamado any2any que ayuda a hacer transformaciones complejas entre dos tipos de Python.

En su caso, supongo que desea transformar de un diccionario (obtenido con json.loads ) a un objeto complejo response.education ; response.name response.education ; response.name , con una estructura anidada response.education.id , etc … Así que eso es exactamente para lo que está hecho este marco. La documentación aún no es excelente, pero al usar any2any.simple.MappingToObject , debería poder hacerlo muy fácilmente. Por favor pregunta si necesitas ayuda.

Si está usando Python 3.5+, puede usar jsons para serializar y deserializar para jsons objetos antiguos de 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() 

También puede hacer que FbApiUser herede de jsons.JsonSerializable para más elegancia:

 user = FbApiUser.from_json(response) 

Estos ejemplos funcionarán si su clase consta de tipos predeterminados de Python, como cadenas, números enteros, listas, jsons , etc. Sin jsons , la jsons requerirá sugerencias de tipo para tipos personalizados.

Modificando un poco la respuesta de @DS, para cargar desde un archivo:

 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) 

Una cosa: esto no puede cargar artículos con números por delante. Me gusta esto:

 { "1_first_item": { "A": "1", "B": "2" } } 

Debido a que “1_first_item” no es un nombre de campo de python válido.

Python3.x

El mejor enfoque que pude alcanzar con mi conocimiento fue este.
Tenga en cuenta que este código trata a set () también.
Este enfoque es genérico y solo necesita la extensión de clase (en el segundo ejemplo).
Tenga en cuenta que solo lo hago con archivos, pero es fácil modificar el comportamiento a su gusto.

Sin embargo esto es un CoDec.

Con un poco más de trabajo puedes construir tu clase de otras maneras. Supongo que un constructor predeterminado para la instancia, luego actualizo la clase 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(ba) print(bb) print(bca) 

Editar

Con un poco más de investigación, encontré una forma de generalizar sin necesidad de la llamada al método de registro SUPERCLASS , utilizando una metaclase

 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(ba) # 1 print(bb) # {1, 2} print(bca) # 1230 print(bccmill) # s 

Mientras buscaba una solución, me topé con esta publicación del blog: https://blog.mosthege.net/2016/11/12/json-deserialization-of-nested-objects/

Utiliza la misma técnica que se indicó en las respuestas anteriores pero con un uso de decoradores. Otra cosa que encontré útil es el hecho de que devuelve un objeto escrito al final de la deserialización

 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) 

Uso:

 @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) 

Ampliando un poco la respuesta de DS, si necesita que el objeto sea mutable (lo que se denomina tupla no es), puede usar la biblioteca recordclass en lugar de nameduuple:

 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())) 

El objeto modificado se puede volver a convertir a json fácilmente usando simplejson :

 x.name = "John Doe" new_json = simplejson.dumps(x) 

Ya que nadie proporcionó una respuesta como la mía, la voy a publicar aquí.

Es una clase robusta que puede convertir fácilmente de un lado a otro entre json str y dict que he copiado de mi respuesta a otra pregunta :

 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) 

Si está usando python 3.6+, puede usar marshmallow-dataclass . Contrariamente a todas las soluciones enumeradas anteriormente, es simple y de tipo seguro:

 from marshmallow_dataclass import dataclass @dataclass class User: name: str user, err = User.Schema().load({"name": "Ramirez"}) 

Utilice el módulo json ( nuevo en Python 2.6 ) o el módulo simplejson que casi siempre está instalado.