web-dev-qa-db-fra.com

Le moyen le plus simple de sérialiser un objet de classe simple avec simplejson?

J'essaie de sérialiser une liste d'objets python avec JSON (avec simplejson) et j'obtiens l'erreur que l'objet "n'est pas sérialisable JSON". 

La classe est une classe simple dont les champs ne sont que des entiers, des chaînes et des flottants, et hérite des champs similaires d’une superclasse parente, par exemple:

class ParentClass:
  def __init__(self, foo):
     self.foo = foo

class ChildClass(ParentClass):
  def __init__(self, foo, bar):
     ParentClass.__init__(self, foo)
     self.bar = bar

bar1 = ChildClass(my_foo, my_bar)
bar2 = ChildClass(my_foo, my_bar)
my_list_of_objects = [bar1, bar2]
simplejson.dump(my_list_of_objects, my_filename)

où foo, bar sont des types simples comme je l’ai mentionné ci-dessus. Le seul problème est que ChildClass a parfois un champ qui fait référence à un autre objet (d'un type autre que ParentClass ou ChildClass).

Quel est le moyen le plus simple de sérialiser cela en tant qu'objet json avec simplejson? Est-il suffisant de le rendre sérialisable en tant que dictionnaire? Est-ce le meilleur moyen d'écrire simplement une méthode dict pour ChildClass? Enfin, le fait de se référer à un champ faisant référence à un autre objet complique-t-il considérablement les choses? Si c'est le cas, je peux réécrire mon code pour n'avoir que des champs simples dans les classes (comme des chaînes/floats, etc.)

je vous remercie.

26
user248237dfsf

J'ai déjà utilisé cette stratégie par le passé et j'en suis très satisfaite: Encodez vos objets personnalisés sous forme de littéraux d'objet JSON (comme Python dicts) avec la structure suivante:

{ '__ClassName__': { ... } }

Il s'agit essentiellement d'une dict à un élément dont la clé unique est une chaîne spéciale qui spécifie le type d'objet codé et dont la valeur est une dict des attributs de l'instance. Si ça a du sens.

Une implémentation très simple d'un encodeur et d'un décodeur (simplifié à partir du code que j'ai utilisé) ressemble à ceci:

TYPES = { 'ParentClass': ParentClass,
          'ChildClass': ChildClass }


class CustomTypeEncoder(json.JSONEncoder):
    """A custom JSONEncoder class that knows how to encode core custom
    objects.

    Custom objects are encoded as JSON object literals (ie, dicts) with
    one key, '__TypeName__' where 'TypeName' is the actual name of the
    type to which the object belongs.  That single key maps to another
    object literal which is just the __dict__ of the object encoded."""

    def default(self, obj):
        if isinstance(obj, TYPES.values()):
            key = '__%s__' % obj.__class__.__name__
            return { key: obj.__dict__ }
        return json.JSONEncoder.default(self, obj)


def CustomTypeDecoder(dct):
    if len(dct) == 1:
        type_name, value = dct.items()[0]
        type_name = type_name.strip('_')
        if type_name in TYPES:
            return TYPES[type_name].from_dict(value)
    return dct

Dans cette implémentation, on suppose que les objets que vous encodez auront une méthode de classe from_dict() qui sait comment recréer une instance à partir d'une dict décodée à partir de JSON.

Il est facile d'étendre l'encodeur et le décodeur pour prendre en charge des types personnalisés (par exemple, des objets datetime).

EDIT, pour répondre à votre modification: L’avantage d’une telle implémentation est qu’elle va automatiquement encoder et décoder les occurrences de tout objet trouvé dans le mappage TYPES. Cela signifie qu'il gérera automatiquement une ChildClass comme ceci:

class ChildClass(object):
    def __init__(self):
        self.foo = 'foo'
        self.bar = 1.1
        self.parent = ParentClass(1)

JSON devrait ressembler à ceci:

{ '__ChildClass__': {
    'bar': 1.1,
    'foo': 'foo',
    'parent': {
        '__ParentClass__': {
            'foo': 1}
        }
    }
}
27
Will McCutchen

Une instance de custom class peut être représentée sous forme de chaîne au format JSON à l'aide de la fonction suivante:

def json_repr(obj):
  """Represent instance of a class as JSON.
  Arguments:
  obj -- any object
  Return:
  String that reprent JSON-encoded object.
  """
  def serialize(obj):
    """Recursively walk object's hierarchy."""
    if isinstance(obj, (bool, int, long, float, basestring)):
      return obj
    Elif isinstance(obj, dict):
      obj = obj.copy()
      for key in obj:
        obj[key] = serialize(obj[key])
      return obj
    Elif isinstance(obj, list):
      return [serialize(item) for item in obj]
    Elif isinstance(obj, Tuple):
      return Tuple(serialize([item for item in obj]))
    Elif hasattr(obj, '__dict__'):
      return serialize(obj.__dict__)
    else:
      return repr(obj) # Don't know how to handle, convert to string
  return json.dumps(serialize(obj))

Cette fonction produira une chaîne au format JSON pour 

  • une instance d'une classe personnalisée,

  • un dictionnaire ayant des instances de classes personnalisées sous forme de feuilles,

  • une liste d'instances de classes personnalisées
8
Alex Kotliarov

Comme spécifié dans la documentation JSON de Python // help(json.dumps) //> 

Vous devez simplement remplacer la méthode default() de JSONEncoder afin de fournir une conversion de type personnalisé et la transmettre sous la forme d'un argument cls.

En voici un que j'utilise pour couvrir les types de données spéciaux de Mongo (datetime et ObjectId)

class MongoEncoder(json.JSONEncoder):
    def default(self, v):
        types = {
            'ObjectId': lambda v: str(v),
            'datetime': lambda v: v.isoformat()
        }
        vtype = type(v).__name__
        if vtype in types:
            return types[type(v).__name__](v)
        else:
            return json.JSONEncoder.default(self, v)     

L'appeler aussi simple que

data = json.dumps(data, cls=MongoEncoder)
2
Tzury Bar Yochay

Si vous utilisez Django, vous pouvez le faire facilement via le module de sérialisation de Django. Plus d'informations peuvent être trouvées ici: https://docs.djangoproject.com/en/dev/topics/serialization/

2
Ozgur Akcali

J'ai un problème similaire, mais la fonction json.dump n'est pas appelée par moi . Donc, pour rendre MyClass JSON sérialisable sans attribuer un encodeur personnalisé à json.dump, il faut que Monkey corrige l'encodeur json.

Commencez par créer votre encodeur dans votre module my_module:

import json

class JSONEncoder(json.JSONEncoder):
    """To make MyClass JSON serializable you have to Monkey patch the json
    encoder with the following code:
    >>> import json
    >>> import my_module
    >>> json.JSONEncoder.default = my_module.JSONEncoder.default
    """
    def default(self, o):
        """For JSON serialization."""
        if isinstance(o, MyClass):
            return o.__repr__()
        else:
            return super(self,o)

class MyClass:
    def __repr__(self):
        return "my class representation"

Ensuite, comme cela est décrit dans le commentaire, Monkey corrige l'encodeur JSON:

import json
import my_module
json.JSONEncoder.default = my_module.JSONEncoder.default

Désormais, même un appel de json.dump dans une bibliothèque externe (où vous ne pouvez pas modifier le paramètre cls) fonctionnera pour vos objets my_module.MyClass.

1
mickours

Je me sens un peu ridicule à propos de mes 2 solutions possibles en le relisant maintenant... Bien sûr, lorsque vous utilisez Django-rest-framework, ce framework possède d'excellentes fonctionnalités intégrées pour résoudre ce problème mentionné ci-dessus.

voir cet exemple de vue de modèle sur leur site web

Si vous n'utilisez pas Django-rest-framework, cela peut quand même vous aider:

J'ai trouvé 2 solutions utiles à ce problème dans cette page: (j'aime le plus la deuxième!)

Solution possible 1 (ou voie à suivre): David Chambers Design a fait une belle solution

J'espère que cela ne dérange pas David de copier-coller son code de solution ici:

Définissez une méthode de sérialisation sur le modèle de l'instance:

def toJSON(self):
import simplejson
return simplejson.dumps(dict([(attr, getattr(self, attr)) for attr in [f.name for f in self._meta.fields]]))

et il a même extrait la méthode ci-dessus, donc c'est plus lisible:

def toJSON(self):
fields = []
for field in self._meta.fields:
    fields.append(field.name)

d = {}
for attr in fields:
    d[attr] = getattr(self, attr)

import simplejson
return simplejson.dumps(d)

S'il vous plaît l'esprit, ce n'est pas ma solution, tous les crédits vont au lien inclus. Je pensais juste que cela devrait être sur le débordement de la pile.

Cela pourrait également être mis en œuvre dans les réponses ci-dessus.

Solution 2:

Ma solution préférable se trouve sur cette page:

http://www.traddicts.org/webdevelopment/flexible-and-simple-json-serialization-for-Django/

A propos, j’ai vu le rédacteur de cette seconde et meilleure solution: est également sur stackoverflow: 

Selaux

J'espère qu'il voit cela et que nous pouvons parler de commencer à implémenter et à améliorer son code dans une solution ouverte?

0
michel.iamit

C'est un peu bidon et je suis sûr qu'il y a probablement beaucoup de problèmes avec ça. Cependant, je produisais un script simple et le problème était que je ne voulais pas sous-classer mon sérialiseur Json pour sérialiser une liste d'objets de modèle. J'ai fini par utiliser la compréhension par liste

Soit: Assets = liste de modelobjects

Code:

myJson = json.dumps([x.__dict__ for x in assets])

Jusqu'ici semble avoir fonctionné à merveille pour mes besoins

0
ThinkBonobo