Google App Engine 模型的 JSON 序列化

Posted

技术标签:

【中文标题】Google App Engine 模型的 JSON 序列化【英文标题】:JSON serialization of Google App Engine models 【发布时间】:2010-12-04 15:04:23 【问题描述】:

我已经搜索了很长时间没有成功。我的项目没有使用 Django,是否有一种简单的方法可以将 App Engine 模型(google.appengine.ext.db.Model)序列化为 JSON,或者我是否需要编写自己的序列化程序?

型号:

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

【问题讨论】:

【参考方案1】:

即使您不使用 django 作为框架,这些库仍然可供您使用。

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

【讨论】:

您是说 serializers.serialize("json", ...) 吗?这会引发“AttributeError:'Photo' 对象没有属性 '_meta'”。仅供参考 - serializers.serialize("xml", Photo.objects.all()) 抛出“AttributeError: type object 'Photo' has no attribute 'objects'”。 serializers.serialize("xml", Photo.all()) 抛出“SerializationError: Non-model object () 在序列化过程中遇到”。【参考方案2】:

您不需要编写自己的“解析器”(解析器可能会将 JSON 转换为 Python 对象),但您仍然可以自己序列化您的 Python 对象。

使用simplejson:

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

【讨论】:

是的,但我不想对每个模型都这样做。我正在尝试找到一种可扩展的方法。 哦,我真的很惊讶我找不到任何最佳实践。我认为应用引擎模型 + rpc + json 是给定的......【参考方案3】:

一个简单的递归函数可用于将实体(和任何引用对象)转换为嵌套字典,该字典可以传递给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

【讨论】:

代码中有一个小错误:你有“output[key] = to_dict(model)”的地方应该是:“output[key] = to_dict(value)”。除此之外它是完美的。谢谢! 此代码在遇到 UserProperty 时会失败。我在最后的 else 中使用“output[key] = str(value)”来解决它,而不是引发错误。 好东西。小的改进是使用 iterkeys() 代替,因为你不在那里使用“prop”。 我还没有尝试过所有可能的类型(日期、GeoPt、...),但似乎数据存储区正是这种方法,到目前为止,它一直在为我工作字符串和整数: developers.google.com/appengine/docs/python/datastore/…所以我不确定你是否需要重新发明***才能序列化为 json:json.dumps(db.to_dict(Photo)) @gentimouton 该方法是新增的。它在 2009 年肯定不存在【参考方案4】:

对于简单的案例,我喜欢文末here提倡的做法:

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

这篇文章还包含,在光谱的另一端,一个复杂的序列化程序类,丰富了 django 的(并且确实需要 _meta - 不知道为什么你会收到关于 _meta 丢失的错误,也许是 @987654322 描述的错误@) 具有序列化计算属性/方法的能力。大多数情况下,您的序列化需要介于两者之间,对于那些人来说,@David Wilson 之类的内省方法可能更可取。

【讨论】:

【参考方案5】:

如果您使用app-engine-patch,它将自动为您声明_meta 属性,然后您可以像在django 模型上通常那样使用django.core.serializers(如在sledge 的代码中)。

App-engine-patch 有一些其他很酷的功能,例如混合身份验证(django + google 帐户),并且 django 的管理部分有效。

【讨论】:

app-engine-patch vs google-app-engine-django vs app engine python sdk附带的django版本有什么区别?据我了解,app-engine-patch 更完整? 我还没有在应用引擎上尝试过django的版本,但我认为它是按原样集成的。如果我没记错的话,google-app-engine-django 会尝试让 django 的模型与 app-engine 一起工作(有一些限制)。 app-engine-patch 直接使用 app-engine 模型,他们只是添加了一些次要的东西。他们的网站上有两者之间的比较。【参考方案6】:

这是我找到的最简单的解决方案。它只需要 3 行代码。

只需在模型中添加一个方法即可返回字典:

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

SimpleJSON 现在可以正常工作了:

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]))

【讨论】:

嘿,谢谢你的提示。这很好用,除了我似乎无法序列化日期字段。我得到:TypeError: datetime.datetime(2010, 5, 1, 9, 25, 22, 891937) is not JSON serializable 您好,感谢您指出问题。解决方案是将日期对象转换为字符串。例如,您可以使用“unicode()”包装对“getattr(self, p)”的调用。我编辑了代码以反映这一点。 要删除 db.Model 的元字段,请使用:dict([(p, unicode(getattr(self, p))) for p in self.properties() if not p.startswith(" _")]) 对于 ndb,请参阅 fredva 的回答。 self.properties() 对我不起作用。我使用了self._properties。整行:return dict([(p, unicode(getattr(self, p))) for p in self._properties])【参考方案7】:

为所有模型类定义了一个方法“Model.properties()”。它返回你寻找的字典。

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

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

请参阅文档中的 Model properties。

【讨论】:

某些对象不是“JSON 可序列化”:TypeError: <google.appengine.ext.db.StringProperty object at 0x4694550> is not JSON serializable【参考方案8】:

要序列化模型,请添加自定义 json 编码器,如以下 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)

这将编码:

作为等格式字符串 (per this suggestion) 的日期, 模型作为其属性的字典, 一个用户作为他的电子邮件。

要解码日期,您可以使用此 javascript

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

注意:感谢用户pydave 编辑此代码使其更具可读性。我最初使用 python 的 if/else 表达式以更少的行表达jsonEncoder,如下所示:(我添加了一些 cmets 并使用了google.appengine.ext.db.to_dict,使其比原来的更清晰。)

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)

【讨论】:

【参考方案9】:

上面 Mtgred 的回答对我来说非常有用——我稍微修改了一下,这样我也可以获得条目的密钥。没有那么几行代码,但它给了我唯一的密钥:

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

【讨论】:

【参考方案10】:

在最新 (1.5.2) 版本的 App Engine SDK 中,db.py 中引入了将模型实例转换为字典的 to_dict() 函数。请参阅release notes。

目前在文档中还没有提到这个函数,但我自己试过了,它可以按预期工作。

【讨论】:

不知道这是否已被删除?当我 from google.appengine.ext import db 并使用 simplejson.dumps(db.to_dict(r)) (其中 r 是 db.Model 子类的实例)时,我得到 AttributeError: 'module' object has no attribute 'to_dict'。我在 google_appengine/google/appengine/ext/db/* 中没有看到“to_dict” 它必须像“db.to_dict(ObjectOfClassModel)”一样使用 对于 ndb 对象, self.to_dict() 完成了这项工作。如果你想让类被标准的 json 模块序列化,添加 'def default(self, o): return o.to_dict()` 到类【参考方案11】:

我已经扩展了dpatru写的JSON Encoder类来支持:

查询结果属性(例如 car.owner_set) ReferenceProperty - 递归地将其转换为 JSON

过滤属性 - 只有带有 verbose_name 的属性才会被编码为 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)

【讨论】:

【参考方案12】:

正如https://***.com/users/806432/fredva 所提到的,to_dict 效果很好。这是我正在使用的代码。

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

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

myJson = json.dumps(prepJson))

【讨论】:

是的,Model 上还有一个“to_dict”……这个函数是让整个问题变得尽可能简单的关键。它甚至适用于具有“结构化”和“重复”属性的 NDB!【参考方案13】:

要序列化数据存储模型实例,您不能使用 json.dumps(尚未测试,但 Lorenzo 指出了这一点)。也许在未来以下会起作用。

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)

【讨论】:

问题是关于将 AppEngine 数据存储模型实例转换为 JSON。您的解决方案只是将 Python 字典转换为 JSON @tunedconsulting 我没有尝试使用 json.dumps 序列化数据存储模型实例,但假设它适用于任何对象。如果文档中没有说明 json.dumps 将对象作为参数,则应提交错误报告。它被添加为评论,仅重新评论它在 2009 年不存在。添加此答案是因为它似乎有点过时,但如果它不起作用,那么我很乐意将其删除。 如果你尝试 json.dumps 一个 entity 对象或 model 类,你会得到 TypeError: 'is not JSON serializable' 。 GAE 的数据存储有自己的数据类型(例如日期)。当前的正确答案,经过测试和工作,是来自 dmw 的答案,它将一些有问题的数据类型转换为可序列化的数据类型。 @tunedconsulting 感谢您对此的意见,我会更新我的答案。【参考方案14】:

不再推荐使用这些 API (google.appengine.ext.db)。使用这些 API 的应用只能在 App Engine Python 2 运行时中运行,并且需要先迁移到其他 API 和服务,然后才能迁移到 App Engine Python 3 运行时。 了解更多:click here

【讨论】:

以上是关于Google App Engine 模型的 JSON 序列化的主要内容,如果未能解决你的问题,请参考以下文章

在 python Google App Engine 中,如何将模型的所有实体导出到 Google Storage 中的文件以供开发人员使用?

使用搜索 API Python - Google App Engine 大表

当一个人创建一个新模型时,应该在哪里放置代码以在 Google App Engine/Django 上自动增加一个分片计数器?

将Google App Engine与Javascript客户端连接

在Google App Engine中,Python DataStore模型是get_by_key_name的结果( )以与输入相同的顺序?

从 Google App Engine 数据存储区获取随机记录?