JSON-Serialisierung von Google App Engine-Modellen

86

Ich habe eine ganze Weile ohne Erfolg gesucht. Mein Projekt verwendet kein Django. Gibt es eine einfache Möglichkeit, App Engine-Modelle (google.appengine.ext.db.Model) in JSON zu serialisieren, oder muss ich meinen eigenen Serializer schreiben?

Modell:

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')
user111677
quelle

Antworten:

62

Eine einfache rekursive Funktion kann verwendet werden, um eine Entität (und alle Verweise) in ein verschachteltes Wörterbuch zu konvertieren, das übergeben werden kann an 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
dmw
quelle
2
Es gibt einen kleinen Fehler im Code: Wenn Sie "output [key] = to_dict (model)" haben, sollte es sein: "output [key] = to_dict (value)". Ansonsten ist es perfekt. Vielen Dank!
Arikfr
1
Dieser Code schlägt fehl, wenn er auf eine UserProperty stößt. Ich habe es umgangen, indem ich im letzten else "output [key] = str (value)" ausgeführt habe, anstatt einen Fehler auszulösen.
Boris Terzic
1
Tolles Zeug. Eine kleine Verbesserung besteht darin, stattdessen iterkeys () zu verwenden, da Sie dort kein "prop" verwenden.
PEZ
7
Ich habe nicht alle möglichen Typen (Datum, GeoPt, ...) ausprobiert, aber es scheint, dass der Datenspeicher genau diese Methode hat und bisher für mich für Zeichenfolgen und Ganzzahlen gearbeitet hat: developer.google.com/appengine/ docs / python / datastore /… Ich bin mir also nicht sicher, ob Sie das Rad neu erfinden müssen, um zu json zu serialisieren:json.dumps(db.to_dict(Photo))
gentimouton
@gentimouton Diese Methode ist neu hinzugekommen. Es gab es 2009 sicherlich nicht
dmw
60

Dies ist die einfachste Lösung, die ich gefunden habe. Es sind nur 3 Codezeilen erforderlich.

Fügen Sie Ihrem Modell einfach eine Methode hinzu, um ein Wörterbuch zurückzugeben:

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

SimpleJSON funktioniert jetzt ordnungsgemäß:

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]))
mtgred
quelle
Hey, danke für den Tipp. Dies funktioniert hervorragend, außer dass ich das Datumsfeld nicht serialisieren kann. Ich bekomme: TypeError: datetime.datetime (2010, 5, 1, 9, 25, 22, 891937) ist nicht JSON serialisierbar
givp
Hallo, danke, dass du auf das Problem hingewiesen hast. Die Lösung besteht darin, das Datumsobjekt in eine Zeichenfolge zu konvertieren. Zum Beispiel können Sie den Aufruf von "getattr (self, p)" mit "unicode ()" umbrechen. Ich habe den Code bearbeitet, um dies widerzuspiegeln.
Zutat
1
Verwenden Sie Folgendes, um die Metafelder von db.Model zu entfernen: dict ([(p, unicode (getattr (self, p))) für p in self.properties (), wenn nicht p.startswith ("_")])
Wonil
für ndb siehe fredvas antwort.
Kenji Noguchi
self.properties () hat bei mir nicht funktioniert. Ich habe self._properties verwendet. Volle Zeile: return dict ([(p, unicode (getattr (self, p))) für p in self._properties])
Eyal Levin
15

In der neuesten Version (1.5.2) des App Engine SDK wurde eine to_dict()Funktion eingeführt, mit der Modellinstanzen in Wörterbücher konvertiert werden db.py. Siehe die Versionshinweise .

Es gibt noch keinen Hinweis auf diese Funktion in der Dokumentation, aber ich habe es selbst versucht und es funktioniert wie erwartet.

fredrikmorken
quelle
Ich frage mich, ob dies entfernt wurde? Ich bekomme, AttributeError: 'module' object has no attribute 'to_dict'wenn ich from google.appengine.ext import dbund benutze simplejson.dumps(db.to_dict(r))(wobei r eine Instanz einer db.Model-Unterklasse ist). Ich sehe "to_dict" nicht in google_appengine / google / appengine / ext / db / *
idbrii
1
es muss wie "db.to_dict (ObjectOfClassModel)"
Dimitry
2
Für ein ndb-Objekt erledigt self.to_dict () die Aufgabe. Wenn Sie die Klasse durch das Standard-JSON-Modul serialisierbar machen möchten, fügen Sie 'def default (self, o): return o.to_dict () `zur Klasse hinzu
Kenji Noguchi
7

Fügen Sie zum Serialisieren von Modellen einen benutzerdefinierten JSON-Encoder wie im folgenden Python hinzu:

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)

Dies wird codieren:

  • ein Datum als Isoformat-Zeichenfolge ( gemäß diesem Vorschlag ),
  • ein Modell als Diktat seiner Eigenschaften,
  • ein Benutzer als seine E-Mail.

Um das Datum zu dekodieren, können Sie dieses Javascript verwenden:

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

Hinweis: Vielen Dank an den Benutzer pydave, der diesen Code bearbeitet hat, um ihn besser lesbar zu machen. Ich hatte ursprünglich Pythons if / else-Ausdrücke verwendet, um jsonEncoderin weniger Zeilen wie folgt auszudrücken : (Ich habe einige Kommentare hinzugefügt und verwendet google.appengine.ext.db.to_dict, um es klarer als das Original zu machen.)

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)
Daniel Patru
quelle
4

Sie müssen keinen eigenen "Parser" schreiben (ein Parser würde JSON vermutlich in ein Python-Objekt verwandeln), aber Sie können Ihr Python-Objekt trotzdem selbst serialisieren.

Verwenden von simplejson :

import simplejson as json
serialized = json.dumps({
    'filename': self.filename,
    'title': self.title,
    'date_taken': date_taken.isoformat(),
    # etc.
})
Jonathan Feinberg
quelle
1
Ja, aber ich möchte dies nicht für jedes Modell tun müssen. Ich versuche einen skalierbaren Ansatz zu finden.
user111677
Oh, und ich bin wirklich überrascht, dass ich hier keine Best Practices finden kann. Ich dachte, App Engine Modell + rpc + json war eine
Selbstverständlichkeit
4

Für einfache Fälle gefällt mir der hier am Ende des Artikels befürwortete Ansatz :

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

Der Artikel enthält am anderen Ende des Spektrums auch eine komplexe Serializer-Klasse, die Djangos bereichert (und erfordert _meta- nicht sicher, warum Fehler über _meta fehlen, möglicherweise den hier beschriebenen Fehler ), mit der Fähigkeit, berechnete Serialisierungen durchzuführen Eigenschaften / Methoden. Die meiste Zeit, die Sie für die Serialisierung benötigen, liegt irgendwo dazwischen, und für diejenigen ist ein introspektiver Ansatz wie der von @David Wilson möglicherweise vorzuziehen.

Alex Martelli
quelle
3

Auch wenn Sie Django nicht als Framework verwenden, stehen Ihnen diese Bibliotheken weiterhin zur Verfügung.

from django.core import serializers
data = serializers.serialize("xml", Photo.objects.all())
Andrew Sledge
quelle
Meinten Sie serializers.serialize ("json", ...)? Das löst "AttributeError: 'Photo' Objekt hat kein Attribut '_meta'". FYI - serializers.serialize ("xml", Photo.objects.all ()) löst "AttributeError: Typ Objekt 'Foto' hat kein Attribut 'Objekte'". serializers.serialize ("xml", Photo.all ()) löst "SerializationError: Nichtmodellobjekt (<class 'model.Photo'>), das während der Serialisierung aufgetreten ist" aus.
user111677
2

Wenn Sie den App-Engine-Patch verwenden , wird das _metaAttribut automatisch für Sie deklariert , und Sie können es verwendendjango.core.serializers Sie es wie gewohnt bei Django-Modellen verwenden (wie im Code des Schlittens).

App-Engine-Patch hat einige andere coole Funktionen wie eine Hybrid-Authentifizierung (Django + Google-Konten), und der Admin-Teil von Django funktioniert.

mtourne
quelle
Was ist der Unterschied zwischen App-Engine-Patch und Google-App-Engine-Django und der Django-Version, die mit App Engine Python SDK geliefert wird? Soweit ich weiß, ist der App-Engine-Patch vollständiger?
user111677
Ich habe die Version von Django in der App Engine noch nicht ausprobiert, aber ich denke, sie ist unverändert integriert. Wenn ich mich nicht irre, versucht google-app-engine-django, das Modell von django mit App-engine zum Laufen zu bringen (mit einigen Einschränkungen). Der App-Engine-Patch verwendet direkt App-Engine-Modelle, sie fügen nur ein paar Kleinigkeiten hinzu. Es gibt einen Vergleich zwischen den beiden auf ihrer Website.
Mtourne
2

Die Antwort von Mtgred oben hat wunderbar für mich funktioniert - ich habe sie leicht modifiziert, damit ich auch den Schlüssel für den Eintrag bekommen konnte. Nicht so wenige Codezeilen, aber es gibt mir den eindeutigen Schlüssel:

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
Victor Van Hee
quelle
2

Ich habe die von dpatru geschriebene JSON Encoder-Klasse erweitert, um Folgendes zu unterstützen:

  • Eigenschaften der Abfrageergebnisse (z. B. car.owner_set)
  • ReferenceProperty - verwandelt es rekursiv in JSON
  • Filtereigenschaften - Nur Eigenschaften mit a verbose_namewerden in JSON codiert

    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)
Yaron Budowski
quelle
2

Wie von https://stackoverflow.com/users/806432/fredva erwähnt , funktioniert das to_dict hervorragend. Hier ist mein Code, den ich benutze.

foos = query.fetch(10)
prepJson = []

for f in foos:
  prepJson.append(db.to_dict(f))

myJson = json.dumps(prepJson))
TrentVB
quelle
Ja, und es gibt auch ein "to_dict" für Model ... diese Funktion ist der Schlüssel, um dieses ganze Problem so trivial zu machen, wie es sein sollte. Es funktioniert sogar für NDB mit "strukturierten" und "wiederholten" Eigenschaften!
Nick Perkins
1

Es gibt eine Methode "Model.properties ()", die für alle Modellklassen definiert ist. Es gibt das Diktat zurück, das Sie suchen.

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

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

Siehe Modelleigenschaften in den Dokumenten.

Jeff Carollo
quelle
Einige Objekte sind nicht "JSON serialisierbar":TypeError: <google.appengine.ext.db.StringProperty object at 0x4694550> is not JSON serializable
idbrii
0

Zum Serialisieren einer Datenspeichermodellinstanz können Sie json.dumps nicht verwenden (nicht getestet, aber Lorenzo hat darauf hingewiesen). Vielleicht funktioniert in Zukunft Folgendes.

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)
HMR
quelle
Die Frage betrifft die Konvertierung einer AppEngine Datastore Model-Instanz in JSON. Bei Ihrer Lösung geht es nur darum, ein Python-Wörterbuch in JSON zu konvertieren
abgestimmt am
@tunedconsulting Ich habe nicht versucht, eine Datenspeichermodellinstanz mit json.dumps zu serialisieren, gehe aber davon aus, dass sie mit jedem Objekt funktioniert. Ein Fehlerbericht sollte gesendet werden, wenn in der Dokumentation nicht angegeben ist, dass json.dumps ein Objekt als Parameter verwendet. Es wird als Kommentar hinzugefügt, mit nur erneutem Kommentar, dass es 2009 nicht existiert hat. Diese Antwort wurde hinzugefügt, da sie etwas veraltet zu sein scheint. Wenn sie jedoch nicht funktionieren würde, würde ich sie gerne entfernen.
HMR
1
Wenn Sie versuchen, ein Entitätsobjekt oder eine Modellklasse mit json.dumps zu versehen, erhalten Sie TypeError: 'ist nicht JSON-serialisierbar' <Objekt bei 0x0xxxxxx>. Der Datenspeicher von GAE verfügt über eigene Datentypen (z. B. Datumsangaben). Die derzeit richtige Antwort, getestet und funktioniert, ist die von dmw, die einige problematische Datentypen in serialisierbare umwandelt.
Tuned
@tunedconsulting Vielen Dank für Ihre Eingabe, ich werde meine Antwort aktualisieren.
HMR