Comment convertir des données JSON en un objet Python

282

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.POSTcontient 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 cet objet JSON en un objet Python pour une utilisation facile?

Sai Krishna
la source
En règle générale, JSON est converti en listes ou dict vanille. Est-ce que c'est ce que tu veux? Ou espérez-vous convertir JSON directement en un type personnalisé?
Shakakai
Je veux le convertir en objet, quelque chose auquel je peux accéder en utilisant le "." . Comme dans l'exemple ci-dessus -> reponse.name, response.education.id etc ....
Sai Krishna
44
L'utilisation de dicts est un moyen faible de faire de la programmation orientée objet. Les dictionnaires sont un très mauvais moyen de communiquer les attentes des lecteurs de votre code. À l'aide d'un dictionnaire, comment pouvez-vous spécifier clairement et de manière réutilisable que certaines paires clé-valeur de dictionnaire sont requises, tandis que d'autres ne le sont pas? Qu'en est-il de la confirmation qu'une valeur donnée se situe dans la plage ou l'ensemble acceptable? Qu'en est-il des fonctions spécifiques au type d'objet avec lequel vous travaillez (alias méthodes)? Les dictionnaires sont pratiques et polyvalents, mais trop de développeurs agissent comme s'ils avaient oublié que Python est un langage orienté objet pour une raison.
Ragoût
1
Il y a une bibliothèque python pour ce github.com/jsonpickle/jsonpickle (commentant car la réponse est trop en dessous dans le fil et ne sera pas accessible.)
meilleurs voeux

Réponses:

357

Vous pouvez le faire sur une seule ligne, en utilisant namedtupleet 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 namedtuplele renameparamètre de .

DS.
la source
8
cela peut entraîner une erreur de valeur, ValueError: les noms de type et les noms de champ ne peuvent pas commencer par un nombre: «123»
PvdL
3
En tant que débutant à Python, je suis intéressé si c'est une chose à sauvegarder également lorsque la sécurité est un problème.
benjist
8
Cela crée une nouvelle classe différente chaque fois que vous rencontrez un objet JSON lors de l'analyse, non?
fikr4n
2
Intéressant. Je pensais que compter sur d.keys()et d.values()répéter dans le même ordre n'était pas garanti, mais j'avais tort. Les documents indiquent: "Si les vues des clés, des valeurs et des éléments sont itérées sans aucune modification intermédiaire du dictionnaire, l'ordre des éléments correspondra directement.". Bon à savoir pour ces petits blocs de code locaux. J'ajouterais cependant un commentaire pour alerter explicitement les responsables du code d'une telle dépendance.
cfi
1
Je ne suis au courant d'aucune opération inverse agréable à usage général. Tout tuple nommé individuel peut être transformé en dict en utilisant x._asdict(), ce qui pourrait aider pour des cas simples.
DS.
127

Consultez la section intitulée Spécialisation du décodage d'objets JSON dans la json documentation 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']

Tout comme un dictionnaire ordinaire.

Shakakai
la source
1
Hé, je lisais juste et j'ai réalisé que les dictionnaires suffiraient, mais je me demandais comment convertir des objets JSON en dictionnaires et comment accéder à ces données à partir du dictionnaire?
Sai Krishna
Génial, c'est presque clair, je voulais juste savoir une petite chose de plus que s'il y a cet objet -> {'education': {'name1': 456, 'name2': 567}}, comment puis-je accéder à ces données?
Sai Krishna
ce serait juste topLevelData ['education'] ['name1'] ==> 456. logique?
Shakakai
1
@Ben: Je pense que votre commentaire est inapproprié. De toutes les réponses ici actuellement, c'est la seule à avoir les bons cours. Ce qui signifie: c'est une opération en un seul passage et le résultat utilise les bons types. Pickle lui-même est destiné à des applications différentes de JSON (représentation binaire ou textuelle) et jsonpickle est une bibliothèque non standard. Je serais intéressé de voir comment vous résolvez le problème selon lequel la bibliothèque std json ne fournit pas l'arbre d'analyse supérieur au crochet objet
cfi
Je dois être d'accord avec @Ben à ce sujet. C'est vraiment une mauvaise solution. Pas évolutif du tout. Vous devrez conserver les noms de champs sous forme de chaîne et de champ. Si vous souhaitez refactoriser vos champs, le décodage échouera (bien sûr, les données déjà sérialisées ne seront plus pertinentes de toute façon). Le même concept est déjà bien implémenté avec jsonpickle
guyarad
99

Ce n'est pas du golf de code, mais voici mon astuce la plus courte, en utilisant types.SimpleNamespacecomme conteneur pour les objets JSON.

Par rapport à la namedtuplesolution leader , c'est:

  • probablement plus rapide / plus petit car il ne crée pas de classe pour chaque objet
  • plus court
  • aucune renameoption, et probablement la même limitation sur les clés qui ne sont pas des identifiants valides (utilise setattrsous 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)
eddygeek
la source
2
Soit dit en passant, la bibliothèque de sérialisation Marshmallow offre une fonctionnalité similaire avec son @post_loaddécorateur. marshmallow.readthedocs.io/en/latest/…
Taylor Edmiston
3
Pour éviter la dépendance à argparse: remplacez l'importation argparse par from types import SimpleNamespaceet utilisez:x = json.loads(data, object_hook=lambda d: SimpleNamespace(**d))
maxschlepzig
8
Ceci est la solution la plus élégante, devrait être au sommet.
ScalaWilliam
4
Modifié pour utiliser la solution de @ maxschlepzig lors de l'exécution sous Python 3.x ( types.SimpleNamespacen'existe pas en 2.7, malheureusement).
Dan Lenski
1
pourquoi print_function?
chwi
90

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 passez les paramètres sous forme de carte.

cmaluenda
la source
1
J'obtiens TypeError: l'objet 'Utilisateur' n'est pas indexable
Mahdi
1
Ce devrait être la réponse acceptée. travaillé pour moi et beaucoup plus simple que tout le reste.
Izik
Je n'ai pas utilisé * args, ** kwargs, mais la solution a fonctionné.
Malkaviano
1
L'utilisateur (** j) dit qu'il manque les paramètres de nom et de nom d'utilisateur, aussi comment le dict est-il initialisé?
Aaron Stainback
40

Voici une alternative rapide et sale aux cornichons 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()
ubershmekel
la source
1
Ce n'est pas une bonne approche. Au début, to_json et from_json ne doivent pas être placés dans votre classe. Au second, cela ne fonctionnera pas pour les classes imbriquées.
Jurass
17

Pour les objets complexes, vous pouvez utiliser JSON Pickle

Bibliothèque Python pour sérialiser tout graphe d'objets arbitraires en JSON. Il peut prendre presque n'importe quel objet Python et transformer l'objet en JSON. De plus, il peut reconstituer l'objet en Python.

spoutnikus
la source
6
Je pense que jsonstruct est mieux. jsonstruct originally a fork of jsonpickle (Thanks guys!). The key difference between this library and jsonpickle is that during deserialization, jsonpickle requires Python types to be recorded as part of the JSON. This library intends to remove this requirement, instead, requires a class to be passed in as an argument so that its definition can be inspected. It will then return an instance of the given class. This approach is similar to how Jackson (of Java) works.
Abhishek Gupta
3
Le problème avec jsonstruct est qu'il ne semble pas être maintenu (en fait, il semble abandonné) et qu'il ne parvient pas à convertir une liste d'objets, comme '[{"name":"object1"},{"name":"object2"}]'. jsonpickle ne le gère pas très bien non plus.
LS
1
Je n'ai aucune idée pourquoi cette réponse n'obtient pas plus de votes. La plupart des autres solutions sont tout à fait là-bas. Quelqu'un a développé une excellente bibliothèque pour le JSON de / sérialisation - pourquoi ne pas l'utiliser? De plus, semble fonctionner correctement avec les listes - quel était votre problème avec @LS?
guyarad
1
@guyarad, le problème est: x = jsonpickle.decode ('[{"name": "object1"}, {"name": "object2"}]') donne une liste de dictionnaires ([{'name': ' object1 '}, {' name ':' object2 '}]), pas une liste d'objets avec des propriétés (x [0] .name ==' object1 '), ce qui était ce que la question d'origine exigeait. Pour l'obtenir, j'ai fini par utiliser l'approche object_hook / Namespace suggérée par eddygeek, mais l'approche rapide / sale par ubershmekel semble également bonne. Je pense que je pourrais utiliser object_hook avec set_encoder_options () de jsonpickle (non documenté!), Mais cela prendrait plus de code que le module json de base. J'adorerais avoir tort!
LS
@LS si vous n'avez aucun contrôle sur l'entrée, ce qui est vraiment ce que l'OP a demandé, jsonpickle n'est pas idéal car il attend le type réel à chaque niveau (et assumera les types de base s'il est manquant). Les deux solutions sont "mignonnes".
guyarad
12

Si vous utilisez Python 3.5+, vous pouvez utiliser jsonspour sérialiser et désérialiser vers de vieux objets Python simples:

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 FbApiUserhériter de jsons.JsonSerializablepour plus d'élégance:

user = FbApiUser.from_json(response)

Ces exemples fonctionneront si votre classe se compose de types par défaut Python, comme des chaînes, des entiers, des listes, des heures, etc. La jsonslib aura cependant besoin d'indices de type pour les types personnalisés.

RH
la source
7

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 de type:

from marshmallow_dataclass import dataclass

@dataclass
class User:
    name: str

user, err = User.Schema().load({"name": "Ramirez"})
lovasoa
la source
TypeError: make_data_class() got an unexpected keyword argument 'many'
JOhn
@JOhn: Vous devriez ouvrir un problème avec un scénario de
lovasoa
6

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

Si vous utilisez python 3.6+, vous pouvez utiliser:
pip install marshmallow-enumet
pip install marshmallow-dataclass

C'est simple et sûr.

Vous pouvez transformer votre classe en une chaîne-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 des classes:

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
danilo
la source
1
Vous n'avez pas besoin du constructeur, passez simplement init = True à la classe de données et vous êtes prêt à partir.
Josef Korbel
5

J'ai écrit un petit framework de (dé) sérialisation appelé any2any qui aide à faire des transformations complexes entre deux types Python.

Dans votre cas, je suppose que vous voulez passer 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 donc exactement pour cela que ce framework est fait. La documentation n'est pas encore géniale, mais en l'utilisant any2any.simple.MappingToObject, vous devriez pouvoir le faire très facilement. Veuillez demander si vous avez besoin d'aide.

sebpiq
la source
Sebpiq, a installé any2any et a du mal à comprendre la séquence prévue d'appels de méthode. Pourriez-vous donner un exemple simple de conversion d'un dictionnaire en objet Python avec une propriété pour chaque clé?
sansjoe
Salut @sansjoe! Si vous l'avez installé depuis pypi, la version est complètement obsolète, j'ai fait un refactoring complet il y a quelques semaines. Vous devriez utiliser la version github (j'ai besoin de faire une bonne version!)
sebpiq
Je l'ai installé à partir de pypy parce que le github a dit de l'installer à partir de pypy. De plus, vous avez dit que Pypy était obsolète il y a des mois .. Cela n'a pas fonctionné :( J'ai déposé un rapport de bug tho! Github.com/sebpiq/any2any/issues/11
sneilan
4

Puisque personne n'a fourni une réponse tout à fait comme la mienne, je vais la poster ici.

C'est une classe robuste qui peut facilement effectuer des conversions entre json stret dictque j'ai copiée 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)
Božo Stojković
la source
2

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 éléments avec des numéros à venir. Comme ça:

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

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

Valtoni Boaventura
la source
2

Lors de la recherche d'une solution, je suis tombé 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 une utilisation de décorateurs. Une autre chose que j'ai trouvée utile est le fait qu'il retourne 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)
enazar
la source
2

En développant un peu la réponse de DS, si vous avez besoin que l'objet soit modifiable (ce qui n'est pas le cas de namedtuple), 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)
BeneStr
la source
1

Si vous utilisez Python 3.6 ou une version plus récente, vous pouvez jeter un œil à squema - un module léger pour les structures de données typées statiquement. Il rend votre code facile à lire tout en offrant une validation, une conversion et une sérialisation des données simples sans travail supplémentaire. Vous pouvez le considérer comme une alternative plus sophistiquée et plus avisée aux couples nommés et aux classes de données. Voici comment vous pouvez l'utiliser:

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()
Funkatic
la source
Ceci est également plus similaire aux méthodes du langage JVM pour le faire.
javadba
1

Je cherchais une solution qui fonctionnait recordclass.RecordClass, prend en charge les objets imbriqués et fonctionne à la fois pour la sérialisation json et la désérialisation json.

En développant la réponse de DS et en développant la solution de BeneStr, j'ai trouvé ce qui semble fonctionner:

Code:

import json
import recordclass

class NestedRec(recordclass.RecordClass):
    a : int = 0
    b : int = 0

class ExampleRec(recordclass.RecordClass):
    x : int       = None
    y : int       = None
    nested : NestedRec = NestedRec()

class JsonSerializer:
    @staticmethod
    def dumps(obj, ensure_ascii=True, indent=None, sort_keys=False):
        return json.dumps(obj, default=JsonSerializer.__obj_to_dict, ensure_ascii=ensure_ascii, indent=indent, sort_keys=sort_keys)

    @staticmethod
    def loads(s, klass):
        return JsonSerializer.__dict_to_obj(klass, json.loads(s))

    @staticmethod
    def __obj_to_dict(obj):
        if hasattr(obj, "_asdict"):
            return obj._asdict()
        else:
            return json.JSONEncoder().default(obj)

    @staticmethod
    def __dict_to_obj(klass, s_dict):
        kwargs = {
            key : JsonSerializer.__dict_to_obj(cls, s_dict[key]) if hasattr(cls,'_asdict') else s_dict[key] \
                for key,cls in klass.__annotations__.items() \
                    if s_dict is not None and key in s_dict
        }
        return klass(**kwargs)

Usage:

example_0 = ExampleRec(x = 10, y = 20, nested = NestedRec( a = 30, b = 40 ) )

#Serialize to JSON

json_str = JsonSerializer.dumps(example_0)
print(json_str)
#{
#  "x": 10,
#  "y": 20,
#  "nested": {
#    "a": 30,
#    "b": 40
#  }
#}

# Deserialize from JSON
example_1 = JsonSerializer.loads(json_str, ExampleRec)
example_1.x += 1
example_1.y += 1
example_1.nested.a += 1
example_1.nested.b += 1

json_str = JsonSerializer.dumps(example_1)
print(json_str)
#{
#  "x": 11,
#  "y": 21,
#  "nested": {
#    "a": 31,
#    "b": 41
#  }
#}
Shriram V
la source
1

Les réponses données ici ne renvoient pas le type d'objet correct, c'est pourquoi j'ai créé ces méthodes ci-dessous. Ils échouent également si vous essayez d'ajouter d'autres champs à la classe qui n'existe pas dans le JSON donné:

def dict_to_class(class_name: Any, dictionary: dict) -> Any:
    instance = class_name()
    for key in dictionary.keys():
        setattr(instance, key, dictionary[key])
    return instance


def json_to_class(class_name: Any, json_string: str) -> Any:
    dict_object = json.loads(json_string)
    return dict_to_class(class_name, dict_object)
Caner
la source
0

Python3.x

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

Il s'agit cependant d'un CoDec.

Avec un peu plus de travail, vous pouvez construire votre classe d'autres manières. Je suppose qu'un constructeur par défaut l'installe, puis je mets à jour le dict de classe.

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)

Éditer

Avec un peu plus de recherche, j'ai trouvé un moyen de généraliser sans avoir besoin de l' appel de méthode de registre SUPERCLASS , en utilisant une métaclasse

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
Davi Abreu Wasserberg
la source
0

Vous pouvez utiliser

x = Map(json.loads(response))
x.__class__ = MyClass

class Map(dict):
    def __init__(self, *args, **kwargs):
        super(Map, self).__init__(*args, **kwargs)
        for arg in args:
            if isinstance(arg, dict):
                for k, v in arg.iteritems():
                    self[k] = v
                    if isinstance(v, dict):
                        self[k] = Map(v)

        if kwargs:
            # for python 3 use kwargs.items()
            for k, v in kwargs.iteritems():
                self[k] = v
                if isinstance(v, dict):
                    self[k] = Map(v)

    def __getattr__(self, attr):
        return self.get(attr)

    def __setattr__(self, key, value):
        self.__setitem__(key, value)

    def __setitem__(self, key, value):
        super(Map, self).__setitem__(key, value)
        self.__dict__.update({key: value})

    def __delattr__(self, item):
        self.__delitem__(item)

    def __delitem__(self, key):
        super(Map, self).__delitem__(key)
        del self.__dict__[key]

Pour une solution générique et pérenne.

Gulzar
la source
-5

Utilisez le jsonmodule ( nouveau dans Python 2.6 ) ou le simplejsonmodule qui est presque toujours installé.

Chris Morgan
la source
2
Hé, merci d'avoir répondu. Pouvez-vous s'il vous plaît publier un exemple de la façon de décoder le JSON puis d'accéder à ces données?
Sai Krishna
Hé, maintenant vous avez un point mais d'une manière ou d'une autre, je préfère le faire sans le savoir et ensuite le désosser: D.
Sai Krishna
1
@Zach: il y a des exemples tout en haut des documents auxquels je suis lié.
Chris Morgan