Serialización JSON de los modelos de Google App Engine

He estado buscando durante bastante tiempo sin éxito. Mi proyecto no utiliza Django. ¿Existe una forma sencilla de serializar los modelos de App Engine (google.appengine.ext.db.Model) en JSON o debo escribir mi propio serializador?

Modelo:

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

Se puede usar una función recursiva simple para convertir una entidad (y cualquier referente) a un diccionario nested que se puede pasar a simplejson :

 import datetime import time SIMPLE_TYPES = (int, long, float, bool, dict, basestring, 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 

Esta es la solución más simple que encontré. Requiere solo 3 líneas de códigos.

Simplemente agregue un método a su modelo para devolver un diccionario:

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

SimpleJSON ahora funciona correctamente:

 class Photo(DictModel): filename = db.StringProperty() title = db.StringProperty() description = db.StringProperty(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])) 

En la última versión (1.5.2) del SDK de App Engine, una función to_dict() que convierte las instancias del modelo en diccionarios se introdujo en db.py Ver las notas de la versión .

No hay ninguna referencia a esta función en la documentación hasta el momento, pero la he probado y funciona como se esperaba.

Para serializar modelos, agregue un codificador json personalizado como en el siguiente python:

 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) 

Esto codificará:

  • una fecha como cadena de isoformato ( según esta sugerencia ),
  • Un modelo como dict de sus propiedades.
  • Un usuario como su correo electrónico.

Para decodificar la fecha puedes usar este javascript:

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

Nota: Gracias al usuario pydave que editó este código para hacerlo más legible. Originalmente había usado las expresiones if / else de python para express jsonEncoder en menos líneas de la siguiente manera: ( google.appengine.ext.db.to_dict algunos comentarios y utilicé google.appengine.ext.db.to_dict , para hacerlo más claro que el 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) 

No es necesario que escriba su propio “analizador” (es probable que un analizador convierta JSON en un objeto de Python), pero aún puede serializar su objeto de Python.

Usando simplejson :

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

Para casos simples, me gusta el enfoque que se aboga aquí al final del artículo:

  # 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())}) 

El artículo también contiene, en el otro extremo del espectro, una clase de serializador complejo que enriquece los django (y requiere _meta – no estoy seguro de por qué faltan errores acerca de _meta, tal vez el error descrito aquí ) con la capacidad de serializar Propiedades / métodos computados. La mayoría de las veces, las necesidades de serialización se encuentran en algún punto intermedio, y para ellas puede ser preferible un enfoque introspectivo como el de @David Wilson.

Incluso si no está utilizando django como marco, esas bibliotecas todavía están disponibles para su uso.

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

Si usas app-engine-patch , declarará automáticamente el atributo _meta por ti, y luego podrás usar django.core.serializers como lo harías normalmente en los modelos django (como en el código del trineo).

App-engine-patch tiene otras características interesantes, como la autenticación híbrida (django + cuentas de google), y la parte de administración de django funciona.

La respuesta de Mtgred funcionó maravillosamente para mí: la modifiqué ligeramente para que también pudiera obtener la clave para la entrada. No como pocas líneas de código, pero me da la clave única:

 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 

He extendido la clase de codificador JSON escrita por dpatru para admitir:

  • Propiedades de resultados de consulta (por ejemplo, car.owner_set)
  • Propiedad de referencia: conviértelo recursivamente en JSON.
  • Propiedades de filtrado: solo las propiedades con un nombre verbose_name se codificarán en 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) 

Como se menciona en https://stackoverflow.com/users/806432/fredva , el to_dict funciona muy bien. Aquí está mi código que estoy usando.

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

Hay un método, “Model.properties ()”, definido para todas las clases de modelos. Devuelve el dictado que buscas.

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

Ver propiedades del modelo en los documentos.

Para serializar una instancia del Modelo del almacén de datos, no puede usar json.dumps (no lo he probado, pero Lorenzo lo señaló). Tal vez en el futuro lo siguiente funcione.

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

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