62 votes

Encodage d'un objet python imbriqué en JSON

Je veux encoder des objets en JSON. Mais, je n'arrive pas à trouver comment faire la sortie sans l'échappement des chaînes.

import json

class Abc:
    def __init__(self):
        self.name="abc name"
    def toJSON(self):
        return json.dumps(self.__dict__, cls=ComplexEncoder)

class Doc:
    def __init__(self):
        self.abc=Abc()
    def toJSON(self):
        return json.dumps(self.__dict__, cls=ComplexEncoder)

class ComplexEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, Abc) or isinstance(obj, Doc):
            return obj.toJSON()
        else:
            return json.JSONEncoder.default(self, obj)

doc=Doc()
print doc.toJSON()

Le résultat est (le dumps renvoie une représentation de chaîne de caractères, c'est pourquoi les " sont échappés)

{"abc": "{\"name\": \"abc name\"}"}

Je veux quelque chose d'un peu différent. Le résultat attendu est

{"abc": {"name": "abc name"}"}

Mais je ne vois pas comment faire... Un conseil ?

Merci d'avance.

0 votes

Voir stackoverflow.com/a/63718624/1497139 pour une réponse à la question plus générale

67voto

Fred Laurent Points 1231

Mon échantillon précédent, avec un autre objet imbriqué et vos conseils :

import json

class Identity:
    def __init__(self):
        self.name="abc name"
        self.first="abc first"
        self.addr=Addr()
    def reprJSON(self):
        return dict(name=self.name, firstname=self.first, address=self.addr) 

class Addr:
    def __init__(self):
        self.street="sesame street"
        self.zip="13000"
    def reprJSON(self):
        return dict(street=self.street, zip=self.zip) 

class Doc:
    def __init__(self):
        self.identity=Identity()
        self.data="all data"
    def reprJSON(self):
        return dict(id=self.identity, data=self.data) 

class ComplexEncoder(json.JSONEncoder):
    def default(self, obj):
        if hasattr(obj,'reprJSON'):
            return obj.reprJSON()
        else:
            return json.JSONEncoder.default(self, obj)

doc=Doc()
print "Str representation"
print doc.reprJSON()
print "Full JSON"
print json.dumps(doc.reprJSON(), cls=ComplexEncoder)
print "Partial JSON"
print json.dumps(doc.identity.addr.reprJSON(), cls=ComplexEncoder)

produit le résultat escompté :

Str representation
{'data': 'all data', 'id': <__main__.Identity instance at 0x1005317e8>}
Full JSON
{"data": "all data", "id": {"name": "abc name", "firstname": "abc first", "address": {"street": "sesame street", "zip": "13000"}}}
Partial JSON
{"street": "sesame street", "zip": "13000"}

Merci.

0 votes

Cela fait presque 9 ans et votre réponse fonctionne toujours très bien avec Python 3.8, c'est bien :) Merci !

31voto

Nicholas Knight Points 9293

Le problème immédiat est donc que vous transmettez au module json une valeur JSON, qui sera encodée comme une autre chaîne dans la valeur JSON.

Le problème plus général est que vous compliquez beaucoup trop les choses.

En s'appuyant sur JSON datetime entre Python et JavaScript j'opterais pour quelque chose de plus proche de ça :

import json

class Abc:
    def __init__(self):
        self.name="abc name"
    def jsonable(self):
        return self.name

class Doc:
    def __init__(self):
        self.abc=Abc()
    def jsonable(self):
        return self.__dict__

def ComplexHandler(Obj):
    if hasattr(Obj, 'jsonable'):
        return Obj.jsonable()
    else:
        raise TypeError, 'Object of type %s with value of %s is not JSON serializable' % (type(Obj), repr(Obj))

doc=Doc()
print json.dumps(doc, default=ComplexHandler)

ce qui vous permet d'obtenir :

~$ python nestjson.py 
{"abc": "abc name"}
~$ 

Cela peut être rendu plus propre/sans danger/sécuritaire (en particulier, il suffit de saisir __dict__ n'est généralement pas une chose recommandée en dehors du débogage/dépannage), mais cela devrait permettre de faire passer le message. Tout ce dont vous avez besoin, fondamentalement, c'est d'un moyen d'obtenir un objet compatible json (qu'il s'agisse d'une simple chaîne ou d'un nombre, ou d'une liste ou d'un dict) à partir de chaque "nœud" de l'arbre. Cet objet doit no être un objet déjà sérialisé en JSON, ce qui est ce que vous faisiez.

13voto

JeanPaulDepraz Points 363

Pour éviter la répétition du code comme dans la réponse de Fred Laurent, j'ai surchargé la fonction __iter__() comme suit. Cela permet également de "jsoniser" les éléments de liste, les dates et les décimales sans aucune dépendance supplémentaire, il suffit d'utiliser dict().

import datetime
import decimal

class Jsonable(object):
    def __iter__(self):
        for attr, value in self.__dict__.iteritems():
            if isinstance(value, datetime.datetime):
                iso = value.isoformat()
                yield attr, iso
            elif isinstance(value, decimal.Decimal):
                yield attr, str(value)
            elif(hasattr(value, '__iter__')):
                if(hasattr(value, 'pop')):
                    a = []
                    for subval in value:
                        if(hasattr(subval, '__iter__')):
                            a.append(dict(subval))
                        else:
                            a.append(subval)
                    yield attr, a
                else:
                    yield attr, dict(value)
            else:
                yield attr, value

class Identity(Jsonable):
    def __init__(self):
        self.name="abc name"
        self.first="abc first"
        self.addr=Addr()

class Addr(Jsonable):
    def __init__(self):
        self.street="sesame street"
        self.zip="13000"

class Doc(Jsonable):
    def __init__(self):
        self.identity=Identity()
        self.data="all data"

def main():
    doc=Doc()
    print "-Dictionary- \n"
    print dict(doc)
    print "\n-JSON- \n"
    print json.dumps(dict(doc), sort_keys=True, indent=4)

if __name__ == '__main__':
    main()

Le résultat :

-Dictionary- 

{'data': 'all data', 'identity': {'first': 'abc first', 'addr': {'street': 'sesame street', 'zip': '13000'}, 'name': 'abc name'}}

-JSON- 

{
    "data": "all data", 
    "identity": {
        "addr": {
            "street": "sesame street", 
            "zip": "13000"
        }, 
        "first": "abc first", 
        "name": "abc name"
    }
}

J'espère que cela vous aidera ! Merci

0 votes

C'est une solution brillante. Ma question est la suivante : comment faire la même chose, mais en limitant le contenu qui peut être jsonisé (par exemple, comment omettre l'attribut "name" de l'objet "Identity") ?

6voto

ced-mos Points 98

Bien que toutes les autres solutions, je suppose qu'elles fonctionnent, je trouve que ils ont beaucoup de code passe-partout lorsque l'objectif est de coder uniquement des objets python imbriqués.

Dans un article J'ai trouvé une solution élégante, qui fait exactement ce que vous avez demandé mais sans le code passe-partout. Comme vous pouvez même avoir la partie désérialisation gratuitement, je vais d'abord vous montrer une solution à votre question exacte et ensuite donner une version plus propre où la désérialisation fonctionnera aussi.

La solution exacte à votre question

import json

class Abc(object):
    def __init__(self):
        self.name = "abc name"

class Doc(object):
    def __init__(self):
        self.abc = Abc()

doc = Doc()

# Serialization
json_data = json.dumps(doc, default=lambda o: o.__dict__)
print(json_data)

Cela donnera exactement ce que vous avez demandé :

{"abc": {"name": "abc name"}}

Une solution plus élégante pour permettre la sérialisation et la dé-sérialisation.

import json

class Abc(object):
    def __init__(self, name: str):
        self.name = name

class Doc(object):
    def __init__(self, abc):
        self.abc = abc

abc = Abc("abc name")
doc = Doc(abc)

# Serialization
json_data = json.dumps(doc, default=lambda o: o.__dict__)
print(json_data)

# De-serialization
decoded_doc = Doc(**json.loads(json_data))
print(decoded_doc)
print(vars(decoded_doc))

Le résultat sera le suivant :

{"abc": {"name": "abc name"}}
<__main__.Doc object at 0x7ff75366f250>
{'abc': {'name': 'abc name'}}

Toute la magie opère en définissant une fonction lambda par défaut : json_data = json.dumps(doc, default=lambda o: o.__dict__) .

2voto

ferhan Points 823

Je n'ai pas pu ajouter ceci comme commentaire et ajouter comme réponse. L'échantillon final de Fred m'a été utile. On m'a dit que jsonpickle faisait cela, mais je n'ai pas réussi à installer et à exécuter le module correctement. J'ai donc utilisé le code ici. Une petite modification cependant, j'avais beaucoup trop de variables à ajouter à la main à certains des objets. Cette petite boucle a donc simplifié les choses :

def reprJSON(self):
    d = dict()
    for a, v in self.__dict__.items():
        if (hasattr(v, "reprJSON")):
            d[a] = v.reprJSON()
        else:
            d[a] = v
    return d

Il peut être utilisé dans tout objet dont la sous-classe est trop chargée pour être codée à la main. On peut aussi en faire une aide pour toutes les classes. Cela fonctionne également pour la présentation JSON complète des tableaux de membres qui contiennent d'autres classes (tant qu'ils implémentent reprJSON() bien sûr).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X