He estado buscando durante bastante tiempo sin éxito. Mi proyecto no está usando Django, ¿hay una forma simple de serializar los modelos de App Engine (google.appengine.ext.db.Model) en JSON o necesito 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')
86
user111677 7 oct. 2009 a las 17:01

12 respuestas

La mejor respuesta

Se puede usar una función recursiva simple para convertir una entidad (y cualquier referente) en un diccionario anidado que se pueda 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
62
dmw 8 jul. 2013 a las 16:37

Para serializar una instancia del modelo de almacén de datos, no puede usar json.dumps (no lo he probado pero Lorenzo lo señaló). Quizás 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)
0
HMR 6 ene. 2014 a las 02:26

Hay un método, "Model.properties ()", definido para todas las clases Model. Le devuelve el dict que busca.

from django.utils import simplejson
class Photo(db.Model):
  # ...

my_photo = Photo(...)
simplejson.dumps(my_photo.properties())

Consulte Propiedades del modelo en los documentos.

1
idbrii 27 ago. 2011 a las 01:39

Según lo mencionado por https://stackoverflow.com/users/806432/fredva, 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))
2
Community 23 may. 2017 a las 12:10

La respuesta de Mtgred anterior funcionó maravillosamente para mí: la modifiqué un poco para que también pudiera obtener la clave de la entrada. No tan 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
2
Victor Van Hee 27 ago. 2010 a las 21:38

Si utiliza app-engine-patch, declarará automáticamente el {{ X0}} para usted, y luego puede usar django.core.serializers como lo haría normalmente en los modelos django (como en el código del trineo).

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

2
mtourne 8 oct. 2009 a las 02:05

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

from django.core import serializers
data = serializers.serialize("xml", Photo.objects.all())
3
Andrew Sledge 7 oct. 2009 a las 13:07

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

  # after obtaining a list of entities in some way, e.g.:
  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é está recibiendo errores sobre _meta faltante, tal vez el error descrito aquí) con la capacidad de serializar propiedades calculadas / métodos. La mayoría de las veces, las necesidades de serialización se encuentran en un punto intermedio, y para aquellos un enfoque introspectivo como @David Wilson puede ser preferible.

4
Alex Martelli 7 oct. 2009 a las 16:23

No necesita escribir su propio "analizador" (un analizador presumiblemente convertiría JSON en un objeto Python), pero aún puede serializar su objeto Python usted mismo.

Usando simplejson:

import simplejson as json
serialized = json.dumps({
    'filename': self.filename,
    'title': self.title,
    'date_taken': date_taken.isoformat(),
    # etc.
})
4
Jonathan Feinberg 7 oct. 2009 a las 13:11

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 un dict de sus propiedades,
  • Una usuaria 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 expresar jsonEncoder en menos líneas de la siguiente manera: (Agregué algunos comentarios y usé 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(<type>)==True if obj is of type <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)
7
Community 23 may. 2017 a las 12:10

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

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

15
fredrikmorken 26 jul. 2011 a las 11:43

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]))
60
mtgred 22 may. 2010 a las 14:08