Comment convertir des données JSON en objet Python
je veux utiliser Python pour convertir des données JSON en un objet Python.
je reçois des objets de données JSON de L'API Facebook, que je souhaite stocker dans ma base de données.
Ma Vue actuelle dans Django (Python) ( request.POST
contient le JSON):
response = request.POST
user = FbApiUser(user_id = response['id'])
user.name = response['name']
user.username = response['username']
user.save()
-
cela fonctionne très bien, mais comment gérer des objets de données JSON complexes?
-
ne serait-ce pas beaucoup mieux si je pouvais convertir cet objet JSON en un objet Python pour une utilisation facile?
11 réponses
vous pouvez le faire en une ligne, en utilisant namedtuple
et 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
ou, pour les réutiliser facilement:
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 vous voulez qu'il manipule des clés qui ne sont pas de bons noms d'Attributs, cochez namedtuple
's rename
paramètre .
consultez la section intitulée décodage spécialisé d'objet JSON dans le json
documentation de module . Vous pouvez utiliser cela pour décoder un objet JSON dans un type Python spécifique.
voici un exemple:
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'>
mise à Jour
si vous voulez accéder à des données dans un dictionnaire via le module json faites ceci:
user = json.loads('{"__type__": "User", "name": "John Smith", "username": "jsmith"}')
print user['name']
print user['username']
comme un dictionnaire.
ce n'est pas du golf de code, mais voici mon truc le plus court, en utilisant types.SimpleNamespace
comme conteneur pour les objets JSON.
par rapport à la première solution namedtuple
, il est:
- probablement plus rapide / plus petit car il ne crée pas une classe pour chaque objet
- plus court
- Non
rename
option, et probablement la même limitation sur les clés qui ne sont pas valides identificateurs (utilisesetattr
sous les couvertures)
exemple:
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)
vous pourriez essayer ceci:
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)
il suffit de créer un nouvel objet, et passer les paramètres comme une carte.
Voici un moyen rapide et sale json pickle alternative
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()
pour les objets complexes, vous pouvez utiliser JSON Pickle
bibliothèque Python pour sérialiser n'importe quel graphique d'objet arbitraire dans JSON. Il peut prendre presque n'importe quel objet Python et transformer l'objet en JSON. En outre, il peut reconstituer l'objet en Python.
j'ai écrit un petit cadre de sérialisation appelé any2any qui aide à faire des transformations complexes entre deux types de Python.
dans votre cas, je suppose que vous voulez transformer d'un dictionnaire (obtenu avec json.loads
) à un objet complexe response.education ; response.name
, avec une structure imbriquée response.education.id
, etc...
C'est exactement ce que ce cadre est fait pour. La documentation n'est pas encore grande, mais en utilisant any2any.simple.MappingToObject
, vous doit être capable de faire cela très facilement. Demandez si vous avez besoin d'aide.
modifier un peu la réponse @DS, pour charger à partir d'un fichier:
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)
une chose: cela ne peut pas charger des articles avec des nombres à l'avance. Comme ceci:
{
"1_first_item": {
"A": "1",
"B": "2"
}
}
parce que" 1_first_item " n'est pas un nom de champ valide de python.
Python3.x
la meilleure approche que j'ai pu atteindre avec ma connaissance était celle-ci.
Notez que ce code de traiter set ().
Cette approche est générique et ne nécessite que l'extension de la classe (dans le deuxième exemple).
Notez que je le fais juste aux fichiers, mais il est facile de modifier le comportement à votre goût.
cependant ceci est un CoDec.
avec un peu plus travail vous pouvez construire votre classe d'autres façons. Je suppose un constructeur par défaut pour l'afficher, puis je mets à jour la classe 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)
modifier
avec un peu plus de recherche j'ai trouvé un moyen de généraliser sans le besoin de la SUPERCLASS appel de méthode de registre, en utilisant un metaclass
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
tout en cherchant une solution, je suis tombé sur ce billet de blog: https://blog.mosthege.net/2016/11/12/json-deserialization-of-nested-objects /
il utilise la même technique que celle décrite dans les réponses précédentes, mais avec un usage de décorateurs. Une autre chose que j'ai trouvé utile est le fait qu'il renvoie un objet dactylographié à la fin de la désérialisation
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)
Utilisation:
@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)
utilisez le json
module ( nouveau en Python 2.6 ) ou le simplejson
module qui est presque toujours installé.