Sérialisation JSON des modèles Google App Engine

Je cherche depuis un certain temps sans succès. Mon projet n’utilise pas Django, existe-t-il un moyen simple de sérialiser les modèles App Engine (google.appengine.ext.db.Model) dans JSON ou dois-je écrire mon propre sérialiseur?

Modèle:

class Photo(db.Model): filename = db.SsortingngProperty() title = db.SsortingngProperty() description = db.SsortingngProperty(multiline=True) date_taken = db.DateTimeProperty() date_uploaded = db.DateTimeProperty(auto_now_add=True) album = db.ReferenceProperty(Album, collection_name='photo') 

Une fonction récursive simple peut être utilisée pour convertir une entité (et tout référent) en un dictionnaire nested pouvant être transmis à simplejson :

 import datetime import time SIMPLE_TYPES = (int, long, float, bool, dict, basessortingng, list) def to_dict(model): output = {} for key, prop in model.properties().iteritems(): value = getattr(model, key) if value is None or isinstance(value, SIMPLE_TYPES): output[key] = value elif isinstance(value, datetime.date): # Convert date/datetime to MILLISECONDS-since-epoch (JS "new Date()"). ms = time.mktime(value.utctimetuple()) * 1000 ms += getattr(value, 'microseconds', 0) / 1000 output[key] = int(ms) elif isinstance(value, db.GeoPt): output[key] = {'lat': value.lat, 'lon': value.lon} elif isinstance(value, db.Model): output[key] = to_dict(value) else: raise ValueError('cannot encode ' + repr(prop)) return output 

C’est la solution la plus simple que j’ai trouvée. Il ne nécessite que 3 lignes de codes.

Ajoutez simplement une méthode à votre modèle pour retourner un dictionnaire:

 class DictModel(db.Model): def to_dict(self): return dict([(p, unicode(getattr(self, p))) for p in self.properties()]) 

SimpleJSON fonctionne maintenant correctement:

 class Photo(DictModel): filename = db.SsortingngProperty() title = db.SsortingngProperty() description = db.SsortingngProperty(multiline=True) date_taken = db.DateTimeProperty() date_uploaded = db.DateTimeProperty(auto_now_add=True) album = db.ReferenceProperty(Album, collection_name='photo') from django.utils import simplejson from google.appengine.ext import webapp class PhotoHandler(webapp.RequestHandler): def get(self): photos = Photo.all() self.response.out.write(simplejson.dumps([p.to_dict() for p in photos])) 

Dans la dernière version (1.5.2) du SDK App Engine, une fonction to_dict() qui convertit les instances de modèle en dictionnaires a été introduite dans db.py Voir les notes de version .

Il n’y a aucune référence à cette fonction dans la documentation pour le moment, mais je l’ai essayée moi-même et cela fonctionne comme prévu.

Pour sérialiser des modèles, ajoutez un encodeur json personnalisé comme dans le python suivant:

 import datetime from google.appengine.api import users from google.appengine.ext import db from django.utils import simplejson class jsonEncoder(simplejson.JSONEncoder): def default(self, obj): if isinstance(obj, datetime.datetime): return obj.isoformat() elif isinstance(obj, db.Model): return dict((p, getattr(obj, p)) for p in obj.properties()) elif isinstance(obj, users.User): return obj.email() else: return simplejson.JSONEncoder.default(self, obj) # use the encoder as: simplejson.dumps(model, cls=jsonEncoder) 

Cela va encoder:

  • une date comme chaîne isoformat ( selon cette suggestion ),
  • un modèle comme un dict de ses propriétés,
  • un utilisateur comme son email.

Pour décoder la date, vous pouvez utiliser ce javascript:

 function decodeJsonDate(s){ return new Date( s.slice(0,19).replace('T',' ') + ' GMT' ); } // Note that this function truncates milliseconds. 

Note: Merci à l’utilisateur pydave qui a édité ce code pour le rendre plus lisible. A l’origine, j’avais utilisé les expressions if / else de python pour exprimer jsonEncoder en moins de lignes: (J’ai ajouté des commentaires et utilisé google.appengine.ext.db.to_dict pour le rendre plus clair que l’original.)

 class jsonEncoder(simplejson.JSONEncoder): def default(self, obj): isa=lambda x: isinstance(obj, x) # isa()==True if obj is of type  return obj.isoformat() if isa(datetime.datetime) else \ db.to_dict(obj) if isa(db.Model) else \ obj.email() if isa(users.User) else \ simplejson.JSONEncoder.default(self, obj) 

Vous n’avez pas besoin d’écrire votre propre “parsingur” (un parsingur transformera probablement JSON en object Python), mais vous pouvez toujours sérialiser votre object Python vous-même.

En utilisant simplejson :

 import simplejson as json serialized = json.dumps({ 'filename': self.filename, 'title': self.title, 'date_taken': date_taken.isoformat(), # etc. }) 

Pour les cas simples, j’aime l’approche préconisée à la fin de l’article:

  # after obtaining a list of entities in some way, eg: user = users.get_current_user().email().lower(); col = models.Entity.gql('WHERE user=:1',user).fetch(300, 0) # ...you can make a json serialization of name/key pairs as follows: json = simplejson.dumps(col, default=lambda o: {o.name :str(o.key())}) 

L’article contient également, à l’autre extrémité du spectre, une classe de sérialiseur complexe qui enrichit les django (et nécessite _meta – pas sûr de savoir pourquoi des erreurs sur _meta manquent, peut-être le bogue décrit ici ) avec la possibilité de sérialiser propriétés / méthodes calculées. La plupart du temps, les besoins en sérialisation se situent quelque part entre les deux, et pour ceux-là, une approche introspective telle que @David Wilson peut être préférable.

Même si vous n’utilisez pas django comme framework, ces bibliothèques sont toujours disponibles.

 from django.core import serializers data = serializers.serialize("xml", Photo.objects.all()) 

Si vous utilisez app-engine-patch, il déclarera automatiquement l’atsortingbut _meta , puis vous pourrez utiliser django.core.serializers comme vous le feriez normalement sur les modèles django (comme dans le code de sledge).

App-engine-patch a quelques autres fonctionnalités intéressantes telles que l’authentification hybride (django + comptes google) et la partie admin de django works.

La réponse de Mtgred ci-dessus a fonctionné à merveille pour moi – je l’ai légèrement modifiée afin que je puisse également obtenir la clé pour l’entrée. Pas autant de lignes de code, mais cela me donne la clé unique:

 class DictModel(db.Model): def to_dict(self): tempdict1 = dict([(p, unicode(getattr(self, p))) for p in self.properties()]) tempdict2 = {'key':unicode(self.key())} tempdict1.update(tempdict2) return tempdict1 

J’ai étendu la classe JSON Encoder écrite par dpatru pour prendre en charge:

  • Propriétés des résultats de la requête (par exemple car.owner_set)
  • ReferenceProperty – transformez-le récursivement en JSON
  • Filtrage des propriétés – seules les propriétés avec un nom verbose_name seront codées dans JSON

     class DBModelJSONEncoder(json.JSONEncoder): """Encodes a db.Model into JSON""" def default(self, obj): if (isinstance(obj, db.Query)): # It's a reference query (holding several model instances) return [self.default(item) for item in obj] elif (isinstance(obj, db.Model)): # Only properties with a verbose name will be displayed in the JSON output properties = obj.properties() filtered_properties = filter(lambda p: properties[p].verbose_name != None, properties) # Turn each property of the DB model into a JSON-serializeable entity json_dict = dict([( p, getattr(obj, p) if (not isinstance(getattr(obj, p), db.Model)) else self.default(getattr(obj, p)) # A referenced model property ) for p in filtered_properties]) json_dict['id'] = obj.key().id() # Add the model instance's ID (optional - delete this if you do not use it) return json_dict else: # Use original JSON encoding return json.JSONEncoder.default(self, obj) 

Comme mentionné par https://stackoverflow.com/users/806432/fredva , le to_dict fonctionne très bien. Voici mon code que j’utilise.

 foos = query.fetch(10) prepJson = [] for f in foos: prepJson.append(db.to_dict(f)) myJson = json.dumps(prepJson)) 

Il existe une méthode, “Model.properties ()”, définie pour toutes les classes du modèle. Il retourne le dict recherché.

 from django.utils import simplejson class Photo(db.Model): # ... my_photo = Photo(...) simplejson.dumps(my_photo.properties()) 

Voir Propriétés du modèle dans les documents.

Pour sérialiser une instance de modèle de magasin de données, vous ne pouvez pas utiliser json.dumps (non testé, mais Lorenzo l’a souligné). Peut-être qu’à l’avenir, ce qui suit fonctionnera.

http://docs.python.org/2/library/json.html

 import json ssortingng = json.dumps(['foo', {'bar': ('baz', None, 1.0, 2)}]) object = json.loads(self.request.body)