web-dev-qa-db-fra.com

Comment convertir des données JSON en objet Python

Je souhaite utiliser Python pour convertir des données JSON en objet Python.

Je reçois des objets de données JSON de l'API Facebook, que je veux 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 bien, mais comment gérer des objets de données JSON complexes? 

  • Ne serait-il pas préférable de convertir en quelque sorte cet objet JSON en objet Python pour une utilisation facile?

191
Sai Krishna

Vous pouvez le faire sur une seule 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 le 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 gère les clés qui ne sont pas de bons noms d'attribut, consultez le paramètre namedtuple ' rename .

270
DS.

Consultez la section intitulée Spécialisation du décodage des objets JSON dans la jsondocumentation du module . Vous pouvez l'utiliser pour décoder un objet JSON en 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'>

Mettre à jour

Si vous souhaitez accéder aux données d'un dictionnaire via le module json, procédez comme suit:

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

Juste comme un dictionnaire ordinaire.

112
Shakakai

Ce n'est pas du code golf, mais voici mon astuce la plus courte: utiliser types.SimpleNamespace en tant que conteneur d'objets JSON.

Par rapport à la solution namedtuple principale, elle est:

  • probablement plus rapide/plus petit car il ne crée pas de classe pour chaque objet
  • plus court
  • pas d'option rename et probablement la même limitation pour les clés qui ne sont pas des identifiants valides (utilise setattr 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)
72
eddygeek

Vous pouvez 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)

Créez simplement un nouvel objet et transmettez les paramètres sous forme de carte.

57
cmaluenda

Voici une alternative rapide et sale 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()
22
ubershmekel

Pour les objets complexes, vous pouvez utiliser JSON Pickle

Bibliothèque Python pour la sérialisation de tout graphe d'objet arbitraire en JSON . Il peut prendre presque n'importe quel objet Python et le transformer en JSON . De plus, il peut reconstituer l'objet dans Python.

14
sputnikus

J'ai écrit un petit cadre de (dé) sérialisation appelé any2any qui permet d'effectuer des transformations complexes entre deux types Python.

Dans votre cas, je suppose que vous souhaitez transformer un dictionnaire (obtenu avec json.loads) en un objet complexe response.education ; response.name, avec une structure imbriquée response.education.id, etc., donc c'est exactement le but de ce cadre. La documentation n'est pas encore excellente, mais en utilisant any2any.simple.MappingToObject, vous devriez pouvoir le faire très facilement. S'il vous plaît demander si vous avez besoin d'aide.

5
sebpiq

Si vous utilisez Python 3.5+, vous pouvez utiliser jsons pour sérialiser et désérialiser les anciens objets 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()

Vous pouvez également faire en sorte que FbApiUser hérite de jsons.JsonSerializable pour plus d'élégance:

user = FbApiUser.from_json(response)

Ces exemples fonctionneront si votre classe comprend des types par défaut Python, tels que des chaînes, des entiers, des listes, des dates/heures, etc. La jsons lib nécessitera toutefois des indicateurs de type pour les types personnalisés.

3
R H

Modification un peu de la réponse @DS, à charger depuis 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 les articles avec des nombres devant. Comme ça:

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

Parce que "1_first_item" n’est pas un nom de champ python valide.

2
Valtoni Boaventura

Python3.x

La meilleure approche que je pouvais atteindre avec ma connaissance était la suivante.
Notez que ce code traite également set ().
Cette approche est générique nécessitant simplement l’extension de la classe (dans le deuxième exemple).
Notez que je ne le fais que pour les fichiers, mais il est facile de modifier le comportement à votre goût.

Cependant, ceci est un CoDec.

Avec un peu plus de travail, vous pouvez construire votre classe de différentes manières ... Je suppose qu'un constructeur par défaut l'illustre, 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

Après quelques recherches supplémentaires, j'ai trouvé un moyen de généraliser sans recourir à l'appel de la méthode de registre DE LA SUPERCLASSE, à l'aide d'un metaclass

import json
import collections

REGISTERED_CLASS = {}

class MetaSerializable(type):

    def __call__(cls, *args, **kwargs):
        if cls.__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
1

Améliorer la très bonne réponse du lovasoa.

Si vous utilisez python 3.6+, vous pouvez utiliser:
pip install Marshmallow-enum et
pip install Marshmallow-dataclass

C'est simple et sûr.

Vous pouvez transformer votre classe en string-json et vice-versa:

De l'objet à la chaîne 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

De la chaîne Json à l'objet:

    json_str = '{"name":"Danilo", "orderId":"50", "productName":"RedBull", "quantity":15, "status":"Created"}'
    user, err = User.Schema().loads(json_str)
    print(user,flush=True)

Définitions de classe:

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
1
danilo

En cherchant une solution, je suis tombé par hasard sur cet article de blog: https://blog.mosthege.net/2016/11/12/json-deserialization-of-nested-objects/

Il utilise la même technique que celle indiquée dans les réponses précédentes, mais avec un usage de décorateurs. Une autre chose que j’ai trouvée utile est le fait qu’il renvoie un objet typé à 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)

Usage:

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

Puisque personne n’a fourni une réponse aussi semblable que la mienne, je vais la poster ici.

Il s'agit d'une classe robuste qui peut facilement convertir entre json str et dict que j'ai copié de ma réponse à une autre question :

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)
0
Božo Stojković

En développant un peu la réponse de DS, si vous avez besoin que l’objet soit mutable (ce que namedtuple n’est pas), vous pouvez utiliser la bibliothèque recordclass au lieu de 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()))

L'objet modifié peut ensuite être reconverti très facilement en json en utilisant simplejson :

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

Si vous utilisez python 3.6+, vous pouvez utiliser Marshmallow-dataclass . Contrairement à toutes les solutions énumérées ci-dessus, il est à la fois simple et sûr:

from Marshmallow_dataclass import dataclass

@dataclass
class User:
    name: str

user, err = User.Schema().load({"name": "Ramirez"})
0
lovasoa