Apache CommonsのPropertyUtilみたいなもの
サーバー側をJAXBで注釈付けたモデルをJSONオブジェクトで吐き出すサービス(RESTEasy+jettison-provider)にして、iPhone側をTouchJSONで受けるようにしたが、ちょっと参照の多いオブジェクトでは、TouchJSONのパース結果はDictionaryとArrayの階層の激しい塊になってしまう。キー値コーディングのkeyPathを使うにしても1つ1つ値を参照するのはちょっとどうだろうな感じ。
なので、キー値コーディングとイントロスペクションを使って自動的にTouchJSONが得たDictionaryを、ユーザーの定義するモデルに変換するものを作成してみた。プロパティの型が曖昧な場合(TouchJSONの結果がNSArrayの要素がNSDictionaryで対象クラスもNSArray型のプロパティでしかなかったり、アブストラクトな型のプロパティだったりの意)のために、型補完するためのAPIを用意することで実現させた。型補完するAPIは、キー値コーディングライクなkeyPathに対応するClassを予め登録しておくというだけのもの。
@interface DictionaryToBeanUtil : NSObject { NSDictionary *_dictionary; NSMutableDictionary *_configureItemTypes; } @property (nonatomic, readonly) NSDictionary *sourceDictionary; @property (nonatomic, readonly) NSDictionary *configureItemTypes; +(void) assignTo:(NSObject*)root from:(NSDictionary*)dictionary; +(void) assignTo:(NSObject*)root from:(NSDictionary*)dictionary types:(NSDictionary*)configureItemTypes; -(id) initWithRootDictionary:(NSDictionary*)dictionary; -(void) configureItemType:(Class)type keyPath:(NSString*)keyPath; -(void) assignTo:(NSObject*)root; @end
ややこしかったのが、Objective-C Runtime Reference 2.0 にあるインストロスペクションを見ちゃっていて、Propertyという型が無かったり、iPhoneに中途半端にしか対応していなかったところ。で、実際は参照しているところが悪くて、こっちが正解だった。ちなみにドキュメントでは、id型のAttributeの説明しかないが(=T@)、NSString* や、MyClass* の場合は、T@"NSString" や T@"MyClass" という表現になる。これを以て、プロパティの型を特定してインスタンスを作成するようにした。ただ、アトリビュートを自前でパースするのが厄介。NSCoderのdecodeXXXも中で絶対パース処理をしているはずだから、ツールとして使えるメソッドが欲しかった。
もう1つややこしいのが、jettison-providerが、ListをJSONにする際に、モデルの持つListプロパティの出力が単数か複数かで、配列表現になったりならなかったりするところだった。jettisonはどうもXML→JSONをしていて、Javaのモデルから判断していないと思われる。だとすれば、同じ名前の要素が連続していたら配列に、1つしか無ければ配列にしない...となるのは道理だ*1。これも変換対象クラスの同じキー(名前)がNSArrayになっていれば1つだろうと自動的に配列にして格納するようにした。
そのうち公開したいけど忙しいので何時かやる。