json から辞書でなくオブジェクトっぽく。
Yahoo!電力使用情況API を使うにあたって JSON をロードしたりしてたんですが、タイミングよくこんな話が。
【辞書にオブジェクトっぽくアクセスする - Keep on moving】
Python 2.6 以降に搭載されている json ライブラリでは、 JSON のロード時にフックを指定することが出来て、上手く使えばロードした JSON をオブジェクトっぽくアクセスできるようになるそうです。
で、 Google App Engine の Python はバージョンが古くて json ライブラリが搭載されていませんが、 Django は標準搭載されているので、そこに含まれる simplejson ライブラリが使える。つまり
from django.utils import simplejson as json
とすれば大抵のコードは流用が可能です。
そして、 simplejson の load にも同様の object_hook 引数がある。つまり、上掲記事と同じことが GAE でも出来る!
さて、 object_hook にはフック用のクラスを渡します。
上掲の id:Ehren さんの記事では別途クラスを定義していましたが、いわゆる無名クラス*1を渡してしまえば、別途の定義も必要なくなります。
test = json.loads('{"foo":[{"a":1},2]}', object_hook=type("D", (dict,), {"__getattr__": dict.__getitem__}))
(とか言いつつ __getattr__ とか __getitem__ とか type() とか初めて触りましたが…。)
しかしまぁ、こうしてみると、 type() を使うと可読性が落ちて見えますね…。
さて、朮の嵌ったところ。
type 関数で dict を継承しようとしたら、下記エラーが。
>>> type("D", (dict), {} ) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: type() argument 2 must be tuple, not type
原因は第2引数。 () で囲っても、中身が1つだとタプルにならないんですね。
>>> (1) 1 >>> (1,) (1,) >>> #というわけで ... type("D", (dict,), {} ) <class '__main__.D'>
dict の後の「,」がポイントです。
余談。
Yahoo!電力使用情況API の JSON は、数値だけのデータに対して "$" という名前をつけています。(たぶん XML から JSON に変換しているんだけど、 Attribute 付きの Element を JSON にするときにノードに名前をつける必要があるから。)
が、どうやら $ は Python 的に特殊なので、上述のように読み込んでも
data.ElectricPowerUsage.Usage.$
とかだと上手くアクセス出来ない。
なので、次のようにアクセスすることになります。
data.ElectricPowerUsage.Usage["$"]
"$" じゃなくてもうちょっとマシな文字を選んでくれたら良かったのに><
*1:でも名前付いてるような…。