Serializacja JSON modeli Google App Engine

86

Szukałem od dłuższego czasu bezskutecznie. Mój projekt nie używa Django, czy istnieje prosty sposób na serializowanie modeli App Engine (google.appengine.ext.db.Model) do JSON, czy też muszę napisać własny serializator?

Model:

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
źródło

Odpowiedzi:

62

Prosta funkcja rekurencyjna może służyć do konwersji jednostki (i dowolnych odnośników) do zagnieżdżonego słownika, który można przekazać do 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
źródło
2
W kodzie jest mały błąd: gdzie masz „output [klucz] = to_dict (model)” powinno być: „output [key] = to_dict (value)”. Poza tym jest doskonały. Dzięki!
arikfr
1
Ten kod nie powiedzie się, gdy napotka właściwość UserProperty. Obejrzałem to, wykonując „output [klucz] = str (wartość)” w ostatnim else, zamiast zgłaszać błąd.
Boris Terzic
1
Świetna sprawa. Niewielkim ulepszeniem jest użycie zamiast tego iterkeys (), ponieważ nie używa się tam "prop".
PEZ
7
Nie wypróbowałem wszystkich możliwych typów (data, GeoPt, ...), ale wygląda na to, że datastore ma dokładnie tę metodę i do tej pory działał dla ciągów i liczb całkowitych: developers.google.com/appengine/ docs / python / datastore /… Więc nie jestem pewien, czy musisz na nowo wymyślać koło, aby serializować do json:json.dumps(db.to_dict(Photo))
gentimouton
@gentimouton Ta metoda jest nowym dodatkiem. Z pewnością nie istniał w 2009
dmw
60

To najprostsze rozwiązanie, jakie znalazłem. Wymaga tylko 3 wierszy kodów.

Po prostu dodaj metodę do swojego modelu, aby zwrócić słownik:

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

SimpleJSON działa teraz poprawnie:

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
źródło
hej dzięki za cynk. to działa świetnie, z wyjątkiem tego, że nie mogę serializować pola daty. Otrzymuję: TypeError: datetime.datetime (2010, 5, 1, 9, 25, 22, 891937) nie można serializować JSON
givp
Cześć, dziękuję za wskazanie problemu. Rozwiązaniem jest przekonwertowanie obiektu daty na ciąg. Na przykład możesz zawinąć wywołanie „getattr (self, p)” za pomocą „unicode ()”. Edytowałem kod, aby to odzwierciedlić.
mtgred
1
Aby usunąć pola meta db.Model, użyj tego: dict ([(p, unicode (getattr (self, p))) for p in self.properties () if not p.startswith ("_")])
Wonil
jeśli chodzi o ndb, zobacz odpowiedź Fredvy.
Kenji Noguchi
self.properties () nie działa dla mnie. Użyłem self._properties. Pełna linia: return dict ([(p, unicode (getattr (self, p))) for p in self._properties])
Eyal Levin
15

W najnowszej (1.5.2) wersji zestawu SDK App Engine wprowadzono to_dict()funkcję konwertującą instancje modelu na słowniki w db.py. Zobacz informacje o wersji .

Na razie w dokumentacji nie ma odniesienia do tej funkcji, ale wypróbowałem ją sam i działa zgodnie z oczekiwaniami.

fredrikmorken
źródło
Zastanawiam się, czy to zostało usunięte? Otrzymuję, AttributeError: 'module' object has no attribute 'to_dict'gdy from google.appengine.ext import dbi używam simplejson.dumps(db.to_dict(r))(gdzie r jest wystąpieniem podklasy db.Model). Nie widzę "to_dict" w google_appengine / google / AppEngine / ext / db / *
idbrii
1
musi być używany jak „db.to_dict (ObjectOfClassModel)”
Dmitry Dushkin,
2
dla obiektu ndb, self.to_dict () wykonuje zadanie. Jeśli chcesz, aby klasa była możliwa do serializacji przez standardowy moduł json, dodaj 'def default (self, o): return o.to_dict ()' do klasy
Kenji Noguchi
7

Aby serializować modele, dodaj niestandardowy koder json, jak w następującym języku 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)

To zakoduje:

  • data jako ciąg isoformat ( zgodnie z tą sugestią ),
  • model jako dyktando jego właściwości,
  • użytkownika jako jego e-mail.

Aby odszyfrować datę, możesz użyć tego javascript:

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

Uwaga: Dziękujemy użytkownikowi pydave, który zmodyfikował ten kod, aby był bardziej czytelny. Pierwotnie użyłem wyrażeń if / else języka Python do wyrażenia jsonEncoderw mniejszej liczbie wierszy w następujący sposób: (Dodałem kilka komentarzy i użyłem google.appengine.ext.db.to_dict, aby uczynić je bardziej przejrzystymi niż oryginał).

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
źródło
4

Nie musisz pisać własnego „parsera” (parser prawdopodobnie zmieniłby JSON w obiekt Pythona), ale nadal możesz samemu serializować swój obiekt Pythona.

Korzystanie z simplejson :

import simplejson as json
serialized = json.dumps({
    'filename': self.filename,
    'title': self.title,
    'date_taken': date_taken.isoformat(),
    # etc.
})
Jonathan Feinberg
źródło
1
Tak, ale nie chcę tego robić dla każdego modelu. Próbuję znaleźć skalowalne podejście.
user111677
och i jestem naprawdę zaskoczony, że nie mogę znaleźć w tej sprawie żadnych najlepszych praktyk. Myślałem, że model silnika aplikacji + rpc + json był podany ...
user111677
4

W prostych przypadkach, lubię podejściem zalecanym tu na końcu artykułu:

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

Artykuł zawiera również, na drugim końcu spektrum, złożoną klasę serializatora, która wzbogaca django (i wymaga _meta- nie wiem, dlaczego otrzymujesz błędy dotyczące braku _meta, być może opisanego tutaj błędu ) z możliwością serializacji obliczonej właściwości / metody. W większości przypadków potrzeby serializacji leżą gdzieś pomiędzy, a dla tych bardziej introspektywne podejście, takie jak @David Wilson, może być preferowane.

Alex Martelli
źródło
3

Nawet jeśli nie używasz django jako frameworka, te biblioteki są nadal dostępne do użycia.

from django.core import serializers
data = serializers.serialize("xml", Photo.objects.all())
Andrew Sledge
źródło
Czy chodziło Ci o serializers.serialize ("json", ...)? Powoduje to wyświetlenie komunikatu „AttributeError: 'Photo' obiekt nie ma atrybutu '_meta'”. FYI - serializers.serialize ("xml", Photo.objects.all ()) zgłasza "AttributeError: type object 'Photo' nie ma atrybutu 'objects'”. serializers.serialize ("xml", Photo.all ()) zgłasza "SerializationError: obiekt inny niż model (<klasa 'model.Photo'>) napotkany podczas serializacji".
user111677
2

Jeśli używasz app-engine-patch , automatycznie zadeklaruje on _metaatrybut za Ciebie, a następnie możesz go używać django.core.serializerstak, jak zwykle robisz to na modelach django (jak w kodzie sledge).

App-engine-patch ma kilka innych fajnych funkcji, takich jak uwierzytelnianie hybrydowe (konta django + Google), a część administracyjna django działa.

mtourne
źródło
Jaka jest różnica między app-engine-patch a google-app-engine-django a wersją django dostarczaną z python sdk App Engine? Z tego, co rozumiem, poprawka do silnika aplikacji jest bardziej kompletna?
user111677
Nie próbowałem wersji django na silniku aplikacji, ale myślę, że jest zintegrowana tak, jak jest. google-app-engine-django, jeśli się nie mylę, próbuje sprawić, by model django działał z silnikiem aplikacji (z pewnymi ograniczeniami). app-engine-patch używa bezpośrednio modeli app-engine, po prostu dodają do niego kilka mniejszych elementów. Na ich stronie internetowej znajduje się porównanie między nimi.
mtourne
2

Powyższa odpowiedź Mtgred zadziałała wspaniale dla mnie - nieznacznie ją zmodyfikowałem, więc mogłem również uzyskać klucz do wpisu. Nie tylko kilka wierszy kodu, ale daje mi unikalny klucz:

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
źródło
2

Rozszerzyłem klasę JSON Encoder napisaną przez dpatru do obsługi:

  • Właściwości wyników zapytania (np. Car.owner_set)
  • ReferenceProperty - rekurencyjnie zamień go na JSON
  • Właściwości filtrowania - tylko właściwości z a verbose_namezostaną zakodowane w formacie 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)
    
Yaron Budowski
źródło
2

Jak wspomniano w https://stackoverflow.com/users/806432/fredva , to_dict działa świetnie. Oto mój kod, którego używam.

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

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

myJson = json.dumps(prepJson))
TrentVB
źródło
tak, i istnieje również „to_dict” w Modelu ... ta funkcja jest kluczem do uczynienia tego całego problemu tak banalnym, jak powinien. Działa nawet w przypadku NDB z właściwościami „ustrukturyzowanymi” i „powtórzonymi”!
Nick Perkins
1

Istnieje metoda „Model.properties ()” zdefiniowana dla wszystkich klas Model. Zwraca dykt, którego szukasz.

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

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

Zobacz Właściwości modelu w dokumentacji.

Jeff Carollo
źródło
Niektórych obiektów nie można serializować w formacie JSON:TypeError: <google.appengine.ext.db.StringProperty object at 0x4694550> is not JSON serializable
idbrii,
1

Te interfejsy API (google.appengine.ext.db) nie są już zalecane. Aplikacje korzystające z tych interfejsów API mogą działać tylko w środowisku wykonawczym App Engine Python 2 i przed migracją do środowiska wykonawczego App Engine Python 3 będą musiały przejść na inne interfejsy API i usługi. Aby dowiedzieć się więcej: kliknij tutaj

Rishabh Jain
źródło
0

Aby serializować instancję Datastore Model, nie możesz użyć json.dumps (nie testowano, ale Lorenzo to wskazał). Może w przyszłości zadziała poniższe.

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
źródło
pytanie dotyczy konwersji instancji AppEngine Datastore Model do formatu JSON. Twoim rozwiązaniem jest tylko konwersja słownika Pythona do formatu JSON
dostrojono
@tunedconsulting Nie próbowałem serializować instancji modelu Datastore za pomocą json.dumps, ale zakładam, że zadziałaby z każdym obiektem. Należy zgłosić błąd, jeśli nie jest tak, jak stwierdza dokumentacja, że ​​json.dumps przyjmuje obiekt jako parametr. Został dodany jako komentarz, z powtórnym komentarzem, że nie istniał w 2009 roku. Dodałem tę odpowiedź, ponieważ wydaje się nieco przestarzała, ale jeśli nie zadziała, z przyjemnością ją usunę.
HMR
1
Jeśli spróbujesz wykonać json.dump obiekt jednostki lub klasę modelu , otrzymasz TypeError: 'is not JSON serializable' <Object at 0x0xxxxxx>. Datastore GAE ma własne typy danych (na przykład dla dat). Obecna prawidłowa odpowiedź, przetestowana i działająca, jest tą z dmw, która przekształca niektóre problematyczne typy danych w nadające się do serializacji.
dostrojono
@tunedconsulting Dziękuję za wkład w tej sprawie, zaktualizuję moją odpowiedź.
HMR