GAEのModelとJavaScriptのモデルをJSON経由で相互変換
GAE と jQuery で JSON をやりとりする準備がととのったので、GAE(Python) の db.Model と、JavaScript で扱うモデルを相互に変換させたい。
基本線としてまずは、
- なるべく変換コードは書きたくない
- あまり汎用性を求めない(複雑な構造は変換できなくてもいいや)
ということで検証を進めていきたいと思います。
というのは、画面側とサーバー側でいちいち変換するコードを書いている時間はないので、柔軟性を犠牲にしても簡単にデータのやりとりを行いたいなぁということです。仕事でやっている訳ではないのと、細切れの時間を見つけながらの作業でもあるので。
JavaScriptのモデル
まず、JavaScript のモデルを用意。グローバルオブジェクト、pmt を用意して、その中に model オブジェクトをつくって、さらにそこに、Project モデル を作成する。今後、モデルの追加は、model オブジェクトの属性として追加していく。
var pmt; (function($){ pmt.model = { Project: function(){ this.object_key = ""; this.name = ""; } }; })(jQuery);
JavaScriptのモデル経由でGAEの呼び出し
ボタンが押された時に、上記で作成したProject モデルのインスタンスを作成し、name プロパティに値をセットした状態で、GAEに送信する。このとき、モデルを JSONとして送信する。
で、結果が返ってきたら(success)、グローバルオブジェクト test.json にセットし、結果モデルとして、再度Projectモデルのインスタンスを作成。
jQuery.extend で結果として返ってきたJSONの内容をProjectモデルにマージ
してみる。JavaScriptオブジェクトの複製はなかなか大変そう なのだが、JSONは、まず、基本文字列ということとで、jQuery.extend() を使ってみる。これは、2つ以上のオブジェクトの内容を、最初の引数として渡されたオブジェクトにマージする関数。
以下ソースでやりたいことは、Projectモデルに、結果JSON の値を解析なしに転記すること。
$("#create_project").click(function(){ var proj = new pmt.model.Project(); proj.name = "new_project"; $.ajax({ url: "/project", type: "POST", dataType: 'json', contentType: "application/json; charset=utf-8", data: JSON.stringify(proj), success: function(data){ test.json = data; var retProj = new pmt.model.Project(); $.extend(retProj,data); alert(retProj.name); } }); });
GAEの受け側のモデルを作成する
GAEのモデルをJSON化するのに、json.dump を使えるのだが、GAEのモデルを直接変換することはできないので、一旦、辞書オブジェクトにGAEのモデルを変換する必要がある。「Google のアプリケーション エンジン モデルの JSON のシリアル化」 を参考に、先ずは一番簡単な方法を実装する。
db.Model と実際のクラスの間で共通に利用するため、上記サイトのDictModel方式をとる。
ただし、Model.key() を クライアントに渡したいので、辞書に、"object_key" を追加してみる。
key() メソッドは、未 put() だと例外を返すらしいので、デフォルトはとりあえずブランクにしておきます。
# -*- coding:utf-8 -*- from google.appengine.ext import db class DictModel(db.Model): ''' http://ja.softuses.com/194765 ''' def to_dict(self): prps = dict([(p, unicode(getattr(self, p))) for p in self.properties()]) object_key = '' try: object_key = unicode(self.key()) except: pass prps['object_key'] = object_key return prps
で、これを継承して、対応するProjectクラスを作成してみる。と。
# -*- coding:utf-8 -*- from google.appengine.ext import db from dictmodel import DictModel class Project(DictModel): name = db.StringProperty(required=True)
クライアントから渡されたJSON文字列をデコードして、Projectインスタンスを作成する
のに、object_hook にデコードする処理を指定します。以下の例では、ラムダ式を使って、Projectのコンストラクタにjsonライブラリにより、解析済みJSONデータ o を渡しています。
class ProjectHandler(webapp2.RequestHandler): def post(self): json_data = self.request.body proj = json.loads(json_data, object_hook=lambda o: Project(**o)) proj.put() logger.info("Project key %s" % unicode(proj.key)) self.response.content_type = 'application/json; charset=utf-8' self.response.out.write(json.dumps(proj.to_dict()))
以下の様に、デコード~結果インスタンス作成を外に出して、きめ細かく処理することもできます。
呼び出されるときに、解析されたJSON文字列が、辞書オブジェクトとして渡されるので、そこから値を取り出して、GAEのModelのコンストラクタの名前付き引数に個別に渡すことができます。
def json_decoder(o): return Project(name=o["name"])
別途定義したデコード処理を、object_hook に渡します。
proj = json.loads(json_data, object_hook=json_decoder)
上記例では、name=o["name"] として、名前付き引数の値に辞書から値を取得して渡していますが、元々のラムダの例のように、辞書オブジェクト o を **o として引数にわたすことで、辞書に含まれている内容を、すべて展開した状態として引数に渡すことができます。つまり、o に “name”:"value" という 要素があれば、name=o["name"] と 展開された状態で、引数として渡されるということです。
実行してみる
ここまでで、まずは、単純な構造のオブジェクトなら、画面とGAE側でSONを経由して極力変換コードを書かずに、モデルのやりとりができる様になりました。複雑な構造や、GAEのModelのフィールドデータ型については、今後の課題として行きたいと思います。
上記で、jQuery.ajax の結果から生成したオブジェクトを、グローバル変数 test.json に入れましたが、Chrome のデバッガで内容を見てみると、きちんと値がやりとりされていることがわかります。
よしよし。