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 のデバッガで内容を見てみると、きちんと値がやりとりされていることがわかります。

gae_json01

よしよし。

Follow me!

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です