Serialisasi JSON model Google App Engine

86

Saya telah mencari cukup lama tanpa hasil. Proyek saya tidak menggunakan Django, apakah ada cara sederhana untuk membuat serial model App Engine (google.appengine.ext.db.Model) menjadi JSON atau apakah saya perlu menulis serializer saya sendiri?

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')
pengguna111677
sumber

Jawaban:

62

Fungsi rekursif sederhana dapat digunakan untuk mengonversi entitas (dan referensi apa pun) menjadi kamus bertingkat yang dapat diteruskan ke 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
sumber
2
Ada kesalahan kecil dalam kode: Di mana Anda memiliki "output [key] = to_dict (model)" seharusnya: "output [key] = to_dict (nilai)". Selain itu sempurna. Terima kasih!
arikfr
1
Kode ini akan gagal jika menemukan UserProperty. Saya mengatasinya dengan melakukan "output [key] = str (value)" di final lain, alih-alih memunculkan kesalahan.
Boris Terzic
1
Barang bagus. Perbaikan kecil adalah dengan menggunakan iterkeys () sebagai gantinya karena Anda tidak menggunakan "prop" di sana.
PEZ
7
Saya belum mencoba semua jenis yang mungkin (tanggal, GeoPt, ...), tetapi tampaknya datastore memiliki metode ini persis, dan sejauh ini telah berfungsi untuk string dan bilangan bulat: developers.google.com/appengine/ docs / python / datastore /… Jadi saya tidak yakin Anda perlu menemukan kembali roda untuk membuat serial ke json:json.dumps(db.to_dict(Photo))
gentimouton
@gentimouton Metode itu adalah tambahan baru. Ini pasti tidak ada pada tahun 2009
dmw
60

Ini adalah solusi paling sederhana yang saya temukan. Ini hanya membutuhkan 3 baris kode.

Cukup tambahkan metode ke model Anda untuk mengembalikan kamus:

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

SimpleJSON sekarang berfungsi dengan baik:

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
sumber
hei terima kasih atas tipnya. ini bekerja dengan baik kecuali saya tidak bisa membuat serial bidang tanggal. Saya mendapatkan: TypeError: datetime.datetime (2010, 5, 1, 9, 25, 22, 891937) tidak dapat diserialkan JSON
givp
Hai, terima kasih telah menunjukkan masalahnya. Solusinya adalah mengubah objek tanggal menjadi string. Misalnya Anda dapat menggabungkan panggilan ke "getattr (self, p)" dengan "unicode ()". Saya mengedit kode untuk mencerminkan ini.
mtgred
1
Untuk menghapus bidang meta db.Model, gunakan ini: dict ([(p, unicode (getattr (self, p))) untuk p di self.properties () jika bukan p.startswith ("_")])
Wonil
untuk ndb, lihat jawaban fredva.
Kenji Noguchi
self.properties () tidak berhasil untuk saya. Saya menggunakan self._properties. Baris lengkap: return dict ([(p, unicode (getattr (self, p))) untuk p in self._properties])
Eyal Levin
15

Dalam rilis terbaru (1.5.2) dari App Engine SDK, to_dict()fungsi yang mengonversi instance model menjadi kamus diperkenalkan di db.py. Lihat catatan rilis .

Belum ada referensi ke fungsi ini dalam dokumentasi, tetapi saya telah mencobanya sendiri dan berfungsi seperti yang diharapkan.

fredrikmorken.dll
sumber
Saya ingin tahu apakah ini telah dihapus? Saya mendapatkan AttributeError: 'module' object has no attribute 'to_dict'ketika saya from google.appengine.ext import dbdan menggunakan simplejson.dumps(db.to_dict(r))(di mana r adalah turunan dari subkelas db.Model). Saya tidak melihat "to_dict" di google_appengine / google / appengine / ext / db / *
idbrii
1
itu harus digunakan seperti "db.to_dict (ObjectOfClassModel)"
Dmitry Dushkin
2
untuk objek ndb, self.to_dict () melakukan tugasnya. Jika Anda ingin membuat kelas dapat diserialkan dengan modul json standar, tambahkan 'def default (self, o): return o.to_dict () `ke kelas
Kenji Noguchi
7

Untuk membuat model berseri, tambahkan encoder json kustom seperti pada python berikut:

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)

Ini akan menyandikan:

  • tanggal sebagai string isoformat ( sesuai saran ini ),
  • model sebagai perintah dari propertinya,
  • pengguna sebagai emailnya.

Untuk memecahkan kode tanggal Anda dapat menggunakan javascript ini:

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

Catatan: Terima kasih kepada pengguna pydave yang telah mengedit kode ini agar lebih mudah dibaca. Saya awalnya menggunakan ekspresi if / else python untuk diekspresikan jsonEncoderdalam lebih sedikit baris sebagai berikut: (Saya telah menambahkan beberapa komentar dan menggunakan google.appengine.ext.db.to_dict, untuk membuatnya lebih jelas daripada aslinya.)

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
sumber
4

Anda tidak perlu menulis "parser" Anda sendiri (pengurai mungkin akan mengubah JSON menjadi objek Python), tetapi Anda masih bisa membuat serialisasi objek Python Anda sendiri.

Menggunakan simplejson :

import simplejson as json
serialized = json.dumps({
    'filename': self.filename,
    'title': self.title,
    'date_taken': date_taken.isoformat(),
    # etc.
})
Jonathan Feinberg
sumber
1
Ya, tapi saya tidak ingin melakukan ini untuk setiap model. Saya mencoba menemukan pendekatan yang dapat diskalakan.
pengguna111677
oh dan saya sangat terkejut bahwa saya tidak dapat menemukan praktik terbaik tentang ini. Saya pikir model mesin aplikasi + rpc + json diberikan ...
pengguna111677
4

Untuk kasus sederhana, saya menyukai pendekatan yang dianjurkan di sini di akhir artikel:

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

Artikel ini juga berisi, di ujung lain spektrum, kelas serializer kompleks yang memperkaya django (dan memang membutuhkan _meta- tidak yakin mengapa Anda mendapatkan kesalahan tentang _meta hilang, mungkin bug yang dijelaskan di sini ) dengan kemampuan untuk membuat serial yang dihitung properti / metode. Sebagian besar waktu yang Anda butuhkan serialisasi terletak di antara keduanya, dan bagi mereka pendekatan introspektif seperti @David Wilson's mungkin lebih disukai.

Alex Martelli
sumber
3

Bahkan jika Anda tidak menggunakan django sebagai kerangka kerja, pustaka tersebut masih tersedia untuk Anda gunakan.

from django.core import serializers
data = serializers.serialize("xml", Photo.objects.all())
Andrew Sledge
sumber
Apakah yang Anda maksud serializers.serialize ("json", ...)? Itu melempar "AttributeError: objek 'Foto' tidak memiliki atribut '_meta'". FYI - serializers.serialize ("xml", Photo.objects.all ()) melempar "AttributeError: tipe objek 'Foto' tidak memiliki atribut 'objek'". serializers.serialize ("xml", Photo.all ()) melempar "SerializationError: Objek non-model (<class 'model.Photo'>) yang ditemukan selama serialisasi".
pengguna111677
2

Jika Anda menggunakan app-engine-patch, itu akan secara otomatis mendeklarasikan _metaatribut untuk Anda, dan kemudian Anda dapat menggunakannya django.core.serializersseperti yang biasa Anda lakukan pada model django (seperti dalam kode sledge).

App-engine-patch memiliki beberapa fitur keren lainnya seperti otentikasi hybrid (akun django + google), dan bagian admin dari django berfungsi.

mtourne.dll
sumber
apa perbedaan antara app-engine-patch vs google-app-engine-django vs versi django yang dikirimkan dengan app engine python sdk? Dari apa yang saya pahami, app-engine-patch lebih lengkap?
pengguna111677
Saya belum mencoba versi django di mesin aplikasi, tapi saya rasa itu terintegrasi apa adanya. google-app-engine-django jika saya tidak salah mencoba membuat model django bekerja dengan app-engine (dengan beberapa batasan). app-engine-patch menggunakan model mesin aplikasi secara langsung, mereka hanya menambahkan beberapa hal kecil ke dalamnya. Ada perbandingan antara keduanya di situs web mereka.
mtourne
2

Jawaban Mtgred di atas bekerja dengan sangat baik bagi saya - saya sedikit memodifikasinya sehingga saya juga bisa mendapatkan kunci untuk entri tersebut. Tidak sesedikit baris kode, tetapi memberi saya kunci unik:

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
sumber
2

Saya telah memperluas kelas JSON Encoder yang ditulis oleh dpatru untuk mendukung:

  • Properti hasil kueri (mis. Car.owner_set)
  • ReferenceProperty - mengubahnya secara rekursif menjadi JSON
  • Properti pemfilteran - hanya properti dengan a yang verbose_nameakan dikodekan ke dalam 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
sumber
2

Seperti yang disebutkan oleh https://stackoverflow.com/users/806432/fredva , to_dict berfungsi dengan baik. Ini kode yang saya gunakan.

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

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

myJson = json.dumps(prepJson))
TrentVB
sumber
ya, dan ada juga "to_dict" pada Model ... fungsi ini adalah kunci untuk membuat semua masalah ini sepele sebagaimana mestinya. Ia bahkan bekerja untuk NDB dengan properti "terstruktur" dan "berulang"!
Nick Perkins
1

Ada metode, "Model.properties ()", yang ditentukan untuk semua kelas Model. Ini mengembalikan perintah yang Anda cari.

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

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

Lihat Properti model di dokumen.

Jeff Carollo
sumber
Beberapa objek tidak "dapat bersambung JSON":TypeError: <google.appengine.ext.db.StringProperty object at 0x4694550> is not JSON serializable
idbrii
1

API ini (google.appengine.ext.db) tidak lagi direkomendasikan. Aplikasi yang menggunakan API ini hanya dapat berjalan di runtime App Engine Python 2 dan perlu bermigrasi ke API dan layanan lain sebelum bermigrasi ke runtime App Engine Python 3. Untuk mengetahui lebih lanjut: klik di sini

Rishabh Jain
sumber
0

Untuk membuat serial instance Model Datastore, Anda tidak dapat menggunakan json.dumps (belum diuji tetapi Lorenzo menunjukkannya). Mungkin di masa depan yang berikut ini akan berhasil.

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
sumber
pertanyaannya adalah tentang mengonversi instance Model AppEngine Datastore ke JSON. Solusi Anda hanya tentang mengubah kamus Python ke JSON
disetel pada
@tunedconsulting Saya belum mencoba membuat serial contoh Model Datastore dengan json.dumps tetapi menganggap itu akan bekerja dengan objek apa pun. Laporan bug harus dikirimkan jika bukan karena dokumentasi menyatakan bahwa json.dumps menggunakan objek sebagai parameter. Itu ditambahkan sebagai komentar dengan hanya komentar ulang yang tidak ada pada tahun 2009. Menambahkan jawaban ini karena tampaknya agak ketinggalan jaman tetapi jika tidak berhasil maka saya akan dengan senang hati menghapusnya.
HMR
1
Jika Anda mencoba untuk json.dumps objek entitas atau kelas model Anda mendapatkan TypeError: 'is not JSON serializable' <Object at 0x0xxxxxx>. GAE's Datastore memiliki tipe data sendiri (untuk tanggal misalnya). Jawaban yang benar saat ini, diuji dan berfungsi, adalah salah satu dari dmw yang mengubah beberapa tipe data bermasalah menjadi yang dapat serial.
disetel pada
@tunedconsulting Terima kasih atas masukannya, jawaban saya akan saya update.
HMR