2012年5月1日火曜日

SBJsonでの自作クラスJSON化

最近色々動けていなかった鬱憤を晴らすかのように2本目です。

何の因果か、ここのところiOSの開発もできるようにならないといけなくてお勉強したりしているわけですが、ちょっとライブラリを使うときにハマったので、軽くがっつりメモしておきます。

iOSでJSON文字列を生成する

google先生によると、どうやらiOSでJSONを使う場合、SBJsonというライブラリを使うのが一般的な様子。
昔はJson.frameworkと呼ばれていたようですが、いまはSBJsonとなっているそうです。

で、2012/05/01現在、SBJsonの最新バージョンは3.1になっていました。
トップページのAPI DocumentationにはVersion 3.1(Alpha)と書かれていますが、githubを見てみるとひと月前くらいにアップデートされているみたいですね。
ちなみに3.0と3.1の差異はARC対応しているかどうかだそうです→http://d.hatena.ne.jp/paraches/20120115/1326650073

ひとまずgithubからバージョン3.1のzipを落としてきて展開します。


インストールの方法はいくつかあるらしいのですが、他でも紹介されているようにソース一式をプロジェクトに追加しました。


これで準備は完了したので、後はコードを書いていきます。

必要なところで#import "SBJson.h"すれば、すぐにパーサが使えるようになるようです。
文字列をオブジェクトに変換するクラスはSBJsonParserで、このクラスのobjectWithStringなどを使うといい感じに変換してくれます。

んで、例えば↓
 [
  {
     "name":"google",
     "url":"www.google.com"
   },
   {
     "name":"yahoo",
     "url":"www.yahoo.com"
   }
 ]
のようなJSON文字列をパースしてオブジェクト化する場合のコードが↓↓
    SBJsonParser *parser = [[SBJsonParser alloc] init];
    NSError *error = nil;
    // jsonStrにJSON文字列が入ってます
    NSArray *resultList = [parser objectWithString:jsonStr error:&error];
    if ( error ) {
        NSLog(@"parse error: %@", jsonStr);
    } else {
        NSLog(@"%@", resultList);
    }
です。

これを実行してみると、
 (
    {
        name = google;
        url = "www.google.com";
    },
    {
        name = yahoo;
        url = "www.yahoo.com";
    }
 )
という感じの出力が得られるはずです。

逆に、配列などをJSONにしたい場合はSBJsonWriterを使います。
例えば、
    SBJsonWriter *writer = [[SBJsonWriter alloc] init];
    NSMutableDictionary *dstDict = [NSMutableDictionary dictionaryWithCapacity:100];
    [dstDict setObject:[NSNumber numberWithDouble:123.27] forKey:@"latitude"];
    [dstDict setObject:[NSNumber numberWithDouble:74.027] forKey:@"longitude"];
    [dstDict setObject:[NSNumber numberWithDouble:0.0001] forKey:@"accuracy"];
    error = nil;
    jsonStr = [writer stringWithObject:dstDict error:&error];
    if ( error ) {
        NSLog(@"parse error: %@", dstDict);
    } else {
        NSLog(@"%@", jsonStr);
    }
のようなコードを実行すると、
{"longitude":74.027000000000001,"latitude":123.27,"accuracy":0.0001}
という文字列を返してくれます。

自作クラスをSBJsonWriterに引数として直接渡す

前フリが長かったですが、ここからが本題です。
SBJsonWriterを使ってJSON文字列を作成する場合、stringWithObjectを使うことになります。
このとき、引数として渡すオブジェクトはなんでもいいのですが、基本的にはNSArrayかNSDictionaryしか渡すことができません。
自作クラスのオブジェクトも渡せますが、普通に渡すとエラーが返ってきます。
しかしどうにかして自作クラスを直接渡したい。

ということで軽くソースを眺めてみると、SBJsonStreamWriter.hの41行目以降に書いてありました。
 If you have a custom class that you want to create a JSON representation for you can implement this method in your class. It should return a representation of your object defined in terms of objects that can be translated into JSON. For example, a Person object might implement it like this: 
 @code
 - (id)proxyForJson {
        return [NSDictionary dictionaryWithObjectsAndKeys:
        name, @"name",
        phone, @"phone",
        email, @"email",
        nil];
 }
 @endcode
ということで、proxyForJsonメソッドを実装すれば良いそうです。

で、実際に実装してみた結果が以下。

MyClassSample.h
@interface MyClassSample : NSObject

@property (nonatomic, strong) NSString *name;
@property (nonatomic) uint age;

- (id)proxyForJson;  // SBJsonを使用して任意のオブジェクトをjsonにする場合,このメソッドを実装する必要がある
@end
MyClassSample.m
#import "MyClassSample.h"

@implementation MyClassSample

@synthesize name;
@synthesize age;

- (id) proxyForJson {
    return [NSDictionary dictionaryWithObjectsAndKeys:
            name, @"name",
            [NSNumber numberWithUnsignedInt:age], @"age", 
            nil];
}

@end
呼び出し側
    MyClassSample *obj1 = [[MyClassSample alloc] init];
    obj1.name = @"name1";
    obj1.age = 21;
    
    error = nil;
    jsonStr = [writer stringWithObject:obj1 error:&error];
    if ( error ) {
        NSLog(@"parse error: %@", obj1);
    } else {
        NSLog(@"%@", jsonStr);
    }
実行結果
{"age":21,"name":"name1"}

ということで、無事に自作クラスをJSON化できました。
ちなみに、自作クラスをNSDictionaryに追加してもちゃんと動作して、
呼び出し側
    MyClassSample *obj1 = [[MyClassSample alloc] init];
    MyClassSample *obj2 = [[MyClassSample alloc] init];
    obj1.name = @"name1";
    obj1.age = 21;
    obj2.name = @"name2";
    obj2.age = 42;
    NSDictionary *userMap = [NSDictionary dictionaryWithObjectsAndKeys:
                             obj1, @"user1", 
                             obj2, @"user2", 
                             nil];
    
    error = nil;
    jsonStr = [writer stringWithObject:userMap error:&error];
    if ( error ) {
        NSLog(@"parse error: %@", userMap);
    } else {
        NSLog(@"%@", jsonStr);
    }
実行結果
{"user2":{"age":42,"name":"name2"},"user1":{"age":21,"name":"name1"}}

という感じになり、こちらも無事にJSON文字列を取ってくることができました。

0 件のコメント:

コメントを投稿