Tuần tự hóa JSON của các mô hình Google App Engine


86

Tôi đã tìm kiếm khá lâu mà không thành công. Dự án của tôi không sử dụng Django, có cách nào đơn giản để tuần tự hóa các mô hình App Engine (google.appengine.ext.db.Model) thành JSON hay tôi cần viết bộ tuần tự của riêng mình?

Mô hình:

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

Câu trả lời:


62

Một hàm đệ quy đơn giản có thể được sử dụng để chuyển đổi một thực thể (và bất kỳ tham chiếu nào) sang một từ điển lồng nhau có thể được chuyển đến 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

2
Có một lỗi nhỏ trong mã: Nơi bạn có "output [key] = to_dict (model)" thì nó phải là: "output [key] = to_dict (value)". Bên cạnh đó nó hoàn hảo. Cảm ơn!
arikfr

1
Mã này sẽ không thành công khi nó gặp UserProperty. Tôi đã giải quyết vấn đề này với việc thực hiện "output [key] = str (value)" trong phần cuối cùng khác, thay vì đưa ra lỗi.
Boris Terzic

1
Công cụ tuyệt vời. Cải thiện nhỏ là sử dụng iterkeys () thay vì bạn không sử dụng "prop" ở đó.
PEZ

7
Tôi đã không thử tất cả các loại có thể có (ngày tháng, GeoPt, ...), nhưng có vẻ như kho dữ liệu có chính xác phương pháp này và nó đang hoạt động cho các chuỗi và số nguyên đối với tôi: Develop.google.com/appengine/ docs / python / kho dữ liệu / ... Vì vậy, tôi không chắc chắn bạn cần phải phát minh lại bánh xe để serialize để json:json.dumps(db.to_dict(Photo))
gentimouton

@gentimouton Phương thức đó là một bổ sung mới. Nó chắc chắn không tồn tại vào năm 2009
dmw

60

Đây là giải pháp đơn giản nhất mà tôi tìm thấy. Nó chỉ yêu cầu 3 dòng mã.

Chỉ cần thêm một phương thức vào mô hình của bạn để trả về một từ điển:

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

SimpleJSON hiện hoạt động bình thường:

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

Này, cảm ơn cho tiền bo. điều này hoạt động tốt ngoại trừ tôi dường như không thể tuần tự hóa trường ngày. Tôi nhận được: TypeError: datetime.datetime (2010, 5, 1, 9, 25, 22, 891937) không thể tuần tự hóa JSON
givp

Xin chào, cảm ơn bạn đã chỉ ra vấn đề. Giải pháp là chuyển đổi đối tượng date thành một chuỗi. Ví dụ: bạn có thể kết hợp lệnh gọi "getattr (self, p)" bằng "unicode ()". Tôi đã chỉnh sửa mã để phản ánh điều này.
mtgred

1
Để loại bỏ các trường meta của db.Model, hãy sử dụng điều này: dict ([(p, unicode (getattr (self, p))) cho p trong self.properties () nếu không phải p.startswith ("_")])
Wonil

cho ndb, hãy xem câu trả lời của fredva.
Kenji Noguchi

self.properties () không hoạt động với tôi. Tôi đã sử dụng self._properties. Dòng đầy đủ: return dict ([(p, unicode (getattr (self, p))) cho p in self._properties])
Eyal Levin

15

Trong phiên bản mới nhất (1.5.2) của App Engine SDK, một to_dict()chức năng chuyển đổi các phiên bản mô hình thành từ điển đã được giới thiệu trong db.py. Xem ghi chú phát hành .

Không có tham chiếu đến chức năng này trong tài liệu, nhưng tôi đã tự mình thử và nó hoạt động như mong đợi.


Tôi tự hỏi nếu điều này đã được gỡ bỏ? Tôi nhận được AttributeError: 'module' object has no attribute 'to_dict'khi tôi from google.appengine.ext import dbvà sử dụng simplejson.dumps(db.to_dict(r))(trong đó r là một thể hiện của lớp con db.Model). Tôi không thấy "to_dict" trong google_appengine / google / appengine / ext / db / *
idbrii

1
nó phải được sử dụng như "db.to_dict (ObjectOfClassModel)"
Dmitry Dushkin

2
đối với một đối tượng ndb, self.to_dict () thực hiện công việc. Nếu bạn muốn làm cho serializable lớp bằng các mô-đun json tiêu chuẩn, thêm 'def mặc định (bản thân, o): return o.to_dict () `đến lớp
Kenji Noguchi

7

Để tuần tự hóa các mô hình, hãy thêm bộ mã hóa json tùy chỉnh như trong python sau:

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)

Điều này sẽ mã hóa:

  • một ngày dưới dạng chuỗi isoformat ( theo gợi ý này ),
  • một mô hình như một chính tả các thuộc tính của nó,
  • một người dùng làm email của anh ấy.

Để giải mã ngày, bạn có thể sử dụng javascript này:

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

Lưu ý: Cảm ơn người dùng pydave đã chỉnh sửa mã này để làm cho nó dễ đọc hơn. Ban đầu, tôi đã sử dụng các biểu thức if / else của python để diễn đạt jsonEncodertrong ít dòng hơn như sau: (Tôi đã thêm một số nhận xét và sử dụng google.appengine.ext.db.to_dict, để làm cho nó rõ ràng hơn so với ban đầu.)

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)

4

Bạn không cần phải viết "trình phân tích cú pháp" của riêng mình (trình phân tích cú pháp có lẽ sẽ biến JSON thành một đối tượng Python), nhưng bạn vẫn có thể tự tuần tự hóa đối tượng Python của mình.

Sử dụng simplejson :

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

1
Có, nhưng tôi không muốn phải làm điều này cho mọi mô hình. Tôi đang cố gắng tìm một cách tiếp cận có thể mở rộng.
dùng111677

ồ và tôi thực sự ngạc nhiên rằng tôi không thể tìm thấy bất kỳ phương pháp hay nhất nào về việc này. Tôi nghĩ mô hình động cơ ứng dụng + rpc + json là một trao ...
user111677

4

Đối với các trường hợp đơn giản, tôi thích cách tiếp cận được đề cập ở đây ở cuối bài viết:

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

Bài báo cũng chứa, ở đầu kia của phổ, một lớp serializer phức tạp làm phong phú thêm django (và không yêu cầu _meta- không chắc tại sao bạn gặp lỗi về _meta bị thiếu, có lẽ lỗi được mô tả ở đây ) với khả năng serialize được tính thuộc tính / phương thức. Hầu hết thời gian bạn cần tuần tự hóa nằm ở đâu đó và đối với những người, cách tiếp cận nội tâm như @David Wilson có thể phù hợp hơn.


3

Ngay cả khi bạn không sử dụng django làm khuôn khổ, những thư viện đó vẫn có sẵn để bạn sử dụng.

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

Ý của bạn là serializers.serialize ("json", ...)? Điều đó ném ra "AttributeError: Đối tượng 'Ảnh' không có thuộc tính '_meta'". FYI - serializers.serialize ("xml", Photo.objects.all ()) ném "AttributeError: nhập đối tượng 'Ảnh' không có thuộc tính 'đối tượng'". serializers.serialize ("xml", Photo.all ()) ném "SerializationError: Non-model object (<class 'model.Photo'>) gặp phải trong quá trình tuần tự hóa".
dùng111677

2

Nếu bạn sử dụng app-engine-patch, nó sẽ tự động khai báo _metathuộc tính cho bạn và sau đó bạn có thể sử dụng django.core.serializersnhư cách bạn thường làm trên các mô hình django (như trong mã của sledge).

App-engine-patch có một số tính năng thú vị khác như xác thực kết hợp (tài khoản django + google) và phần quản trị của django hoạt động.


Sự khác biệt giữa app-engine-patch so với google-app-engine-django và phiên bản django được vận chuyển với app engine python sdk là gì? Theo những gì tôi hiểu, app-engine-patch đã hoàn thiện hơn chưa?
dùng111677

Tôi chưa thử phiên bản django trên công cụ ứng dụng, nhưng tôi nghĩ rằng nó được tích hợp. google-app-engine-django nếu tôi không nhầm thì cố gắng làm cho mô hình của django hoạt động với app-engine (với một số hạn chế). app-engine-patch sử dụng trực tiếp các mô hình app-engine, họ chỉ thêm một số nội dung nhỏ vào đó. Có một so sánh giữa hai trên trang web của họ.
mtourne

2

Câu trả lời của Mtgred ở trên có tác dụng tuyệt vời đối với tôi - tôi đã sửa đổi một chút để tôi cũng có thể lấy được chìa khóa cho mục nhập. Không phải chỉ có vài dòng mã, nhưng nó mang lại cho tôi khóa duy nhất:

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

Tôi đã mở rộng lớp Bộ mã hóa JSON do dpatru viết để hỗ trợ:

  • Thuộc tính kết quả truy vấn (ví dụ: car.owner_set)
  • ReferenceProperty - đệ quy biến nó thành JSON
  • Lọc thuộc tính - chỉ các thuộc tính có a verbose_namemới được mã hóa thành 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)
    

2

Như đã đề cập bởi https://stackoverflow.com/users/806432/fredva , to_dict hoạt động rất tốt. Đây là mã của tôi tôi đang sử dụng.

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

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

myJson = json.dumps(prepJson))

vâng, và cũng có một "to_dict" trên Model ... chức năng này là chìa khóa để làm cho toàn bộ vấn đề này trở nên tầm thường như nó cần phải có. Nó thậm chí hoạt động cho NDB với các thuộc tính "có cấu trúc" và "lặp lại"!
Nick Perkins

1

Có một phương thức, "Model.properties ()", được định nghĩa cho tất cả các lớp Model. Nó trả về mệnh lệnh mà bạn tìm kiếm.

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

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

Xem thuộc tính Mô hình trong tài liệu.


Một số đối tượng không phải là "JSON serializable":TypeError: <google.appengine.ext.db.StringProperty object at 0x4694550> is not JSON serializable
idbrii

1

Các API này (google.appengine.ext.db) không còn được khuyến nghị nữa. Các ứng dụng sử dụng các API này chỉ có thể chạy trong thời gian chạy của App Engine Python 2 và sẽ cần phải di chuyển sang các API và dịch vụ khác trước khi chuyển sang thời gian chạy của App Engine Python 3. Để biết thêm: bấm vào đây


0

Để tuần tự hóa một phiên bản Datastore Model, bạn không thể sử dụng json.dumps (chưa thử nghiệm nhưng Lorenzo đã chỉ ra). Có thể trong tương lai những điều sau sẽ hiệu quả.

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)

câu hỏi là về việc chuyển đổi một phiên bản Mô hình kho dữ liệu AppEngine thành JSON. Giải pháp của bạn chỉ là chuyển đổi từ điển Python sang JSON
được điều chỉnh vào

@tunedconsults Tôi chưa thử tuần tự hóa một phiên bản Mô hình kho dữ liệu với json.dumps nhưng cho rằng nó sẽ hoạt động với bất kỳ đối tượng nào. Một báo cáo lỗi phải được gửi nếu nó không phải như tài liệu nói rằng json.dumps nhận một đối tượng làm tham số. Nó được thêm vào dưới dạng một nhận xét với chỉ nhận xét lại rằng nó không tồn tại vào năm 2009. Đã thêm câu trả lời này vì nó có vẻ hơi lỗi thời nhưng nếu nó không hoạt động thì tôi rất vui khi xóa nó.
HMR

1
Nếu bạn cố gắng json.dumps một đối tượng thực thể hoặc một lớp mô hình, bạn nhận được TypeError: 'is not JSON serializable' <Object at 0x0xxxxxx>. Kho dữ liệu của GAE có các kiểu dữ liệu riêng (ví dụ như ngày tháng). Câu trả lời đúng hiện tại, đã được thử nghiệm và đang hoạt động, là câu trả lời từ dmw có thể chuyển đổi một số kiểu dữ liệu có vấn đề thành kiểu có thể tuần tự hóa.
được điều chỉnh vào

@tunedconsults Cảm ơn bạn đã đóng góp ý kiến ​​về vấn đề này, tôi sẽ cập nhật câu trả lời của mình.
HMR
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.