iOSでパース後のJSONオブジェクトにNSNullが含まれている場合の各種対処方法まとめ

## [追記]

[AFNetworking (2.4.0)](https://github.com/AFNetworking/AFNetworking/releases/tag/2.4.0)で[removesKeysWithNullValues](https://github.com/AFNetworking/AFNetworking/blob/master/AFNetworking/AFURLResponseSerialization.h#L123-L126)が導入されました。
これでResponseSerializerで簡単にNullを削除することができます。めでたしめでたし。

---

##前提知識
iOSでは[NSJSONSerializationクラス](https://developer.apple.com/library/ios/documentation/foundation/reference/nsjsonserialization_class/Reference/Reference.html)でJSONをパースすることができます。

```objective-c
NSError *error = nil;
id json = [NSJSONSerialization JSONObjectWithData:jsonData
                                              options:NSJSONReadingAllowFragments
                                                error:&error];
```

JSONで表現できるデータ型と対応するObjective-Cの型は以下の通りです。

|JSONのデータ型|Objective-C|
|---|---|
|数値（整数、浮動小数点数）|NSNumber|
|文字列（バックスラッシュによるエスケープシーケンス記法を含む、ダブルクォーテーションでくくった文字列）|NSString|
|真偽値（trueとfalse）|NSNumber|
|配列（データのシーケンス）|NSArray|
|オブジェクト（順序づけされていないキーと値のペアの集まり。JSONでは連想配列と等価）|NSDictionary|
|null|NSNull|


※ nullがnilではなくてNSNullオブジェクトというところに注意して下さい。NSArrayやNSDictionaryにはnilは格納できないのでNSNullオブジェクトである必要があります。

##NSNullの何が問題？
例えば、以下のような文字列が1文字以上ある場合に何らかの処理をするコードがあるとします。

```objective-c
NSString *text = [json objectForKey:@"text"];
if (text.length > 0) {
    // something
}
```
objectForKey:で返される値がNSStringクラスだったら問題ないのですが、元の値がnullだった場合にNSNullが返されることになるのでtextはNSNullオブジェクトになります。
その場合、text.lengthで例外("unrecognized selector sent to instance.")が発生しクラッシュします。これはNSNullクラスにはlengthメソッドが実装されていないため発生する例外です。

このような例外を防ぐために、

```objective-c
if ([text isKindOfClass:[NSString class]] && text.length > 0) {
    // something
}
```
or

```objective-c
if ([text respondsToSelector:@selector(length)] && text.length > 0) {
    // something
}
```
といった、メソッドを呼び出す前にクラスやセレクタを確認することによって例外を防ぐのですが、いちいち確認するのは煩雑過ぎます。
通常は上記の様に呼び出し毎に確認するのではなく、モデル等の初期化で確認してその後安全に値にアクセスする方法を取ると思うのですが、やはり確認するコードは煩雑になります。

##対処方法
クラスやセレクタを確認せずに例外を防ぎたいです。
nilだったら上記の例外は発生しません。
ということで対処方法の大筋はnilを利用するかNSNullを削除する方法になります。

##各種対処方法

###1. NSNullをnilとして振る舞わせる
[NullSafe](https://github.com/nicklockwood/nullsafe)

```text:実装
NSNullで例外("unrecognized selector sent to instance.")を発生させないようにしている。
```
```text:導入方法
pod 'NullSafe'
```
```text:使用方法
クラスファイルをプロジェクトに含ませれば自動的に有効になる
```
```text:注意点
プロジェクト全体でNSNullの振る舞いが変更されるのでかなり注意が必要です。
```
```text:利点
現状のプロジェクトを一切いじることなくNSNullの例外を防ぐことができます。(※注意点の問題がない場合)
```

###2. NSNullを一括で削除する
[ISRemoveNull](https://github.com/ishkawa/ISRemoveNull)

```text:実装
NSArray, NSDictionaryの全ての要素にアクセスしてNSNullを削除する。
```
```text:導入方法
pod 'ISRemoveNull'
```
```objective-c:使用例
NSDictionary *strippedDictionary = [dictionary dictionaryByRemovingNull];
```

[NSJSONSerialization-NSNullRemoval](https://github.com/jrturton/NSJSONSerialization-NSNullRemoval)

```text:実装
NSJSONSerializationのカテゴリに実装されているが、やっていることはISRemoveNullと同じ。
```
```text:導入方法
pod 'NSJSONSerialization-NSNullRemoval', :git => 'https://github.com/jrturton/NSJSONSerialization-NSNullRemoval'
```
```objective-c:使用例
stripped = [NSJSONSerialization JSONObjectWithData:data
                                           options:NSJSONReadingMutableContainers
                                             error:nil
                                     removingNulls:YES
                                      ignoreArrays:NO];

```
```text:注意点
削除するために全てのオブジェクトにアクセスする必要があるので要素数やネストが深さに比例してコストがかかる
```
```text:利点
NSArray, NSDictionaryやパース時に個別で削除するかを選択できる。
```

###3. NSNullを効率よく削除する
[CollectionUtils](https://github.com/kishikawakatsumi/CollectionUtils)

```text:実装
2を改良したもの。2ではネストも含む全てのオブジェクトにアクセスしていたのに対してこちらは初期化ではトップのオブジェクトに対してのみNSNullの確認と削除をし、ネストしたオブジェクトへは初めてアクセスしたときに確認して削除する。
```
```text:導入方法
pod "CollectionUtils"
```
```objective-c:使用例
NSArray *array = @[@"0", @"1", [NSNull null], @"2", [NSNull null], @"3"];
NSArray *compactArray = [array cu_compactArray];
//=> ["0", "1", "2", "3"]
//alloc/initを使って生成することも可能
```
```text:利点
3よりも初期化時のコストを抑えることができる。
AFNetworkingに対応しているので(後述)導入が簡単。
```

詳しい解説
[NSArrayやNSDictionaryからNSNullを効率よく取り除く](http://d.hatena.ne.jp/KishikawaKatsumi/20140408/1396968415)

###4. NSNullの場合にnilを返す 
```objective-c:実装例
// Categoryにnilを返すメソッドを追加
@implementation NSDictionary (Additions)

- (id)objectOrNilForKey:(id)aKey
{
    id obj = [self objectForKey:aKey];
    if (obj == [NSNull null]) {
        return nil;
    } else {
        return obj;
    }
}

@end
```
```text:使用例
NSString *text = [json objectOrNilForKey:@"text"];
```
```text:注意点
objectForKey:を置き換える必要がある。
```
```text:利点
アクセス毎にNSNullかnilを利用するか選択できる。
```

##結論
NSNullを削除したければ3の[CollectionUtils](https://github.com/kishikawakatsumi/CollectionUtils)が現状だと最適だと思いました。AFNetworking 2.xのresponseSerializerにも対応したライブラリも[公開](https://github.com/kishikawakatsumi/CollectionUtils#cujsonresponseserializer-for-afnetworking-2x)されているので導入もめちゃめちゃ楽です。

```objective-c:CollectionUtils-AFNetworking
manager.responseSerializer = [CUJSONResponseSerializer serializer];
```
NSNullを残しておきたい場合は、4を使用するのもいいと思います。

##個人的に求めてる解決方法
JSONパースするときnullの場合NSNullオブジェクトをセットしないオプションがあったらいいんじゃないかなって思います。
オブジェクトの順番や個数が必要な場合などNSNullでないといけないケースはもちろんありますが、必要でないケースもまた多くあるかなと。
パース後にどうするかという問題よりかはパーサーで何とかするべき問題じゃないかなって思うんですがどうなんでしょう。

JSONKitでも[ここ](https://github.com/johnezang/JSONKit/issues/24)とか[ここ](https://github.com/johnezang/JSONKit/pull/70)で話題になっていますが対応はされていません。何か問題あるのかな。(※リンク先では空文字列を返せとわけわかんないこと言ってますが)

##参考
[JavaScript Object Notation - wikipedia](http://ja.wikipedia.org/wiki/JavaScript_Object_Notation)
[JSONにNSNullが入ってきたとき](http://blog.ishkawa.org/blog/2013/11/16/nsnull/)
[NSArrayやNSDictionaryからNSNullを効率よく取り除く](http://d.hatena.ne.jp/KishikawaKatsumi/20140408/1396968415)