79
80

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2014-04-11

[追記]

AFNetworking (2.4.0)removesKeysWithNullValuesが導入されました。
これでResponseSerializerで簡単にNullを削除することができます。めでたしめでたし。


##前提知識
iOSではNSJSONSerializationクラスでJSONをパースすることができます。

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文字以上ある場合に何らかの処理をするコードがあるとします。

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メソッドが実装されていないため発生する例外です。

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

if ([text isKindOfClass:[NSString class]] && text.length > 0) {
    // something
}

or

if ([text respondsToSelector:@selector(length)] && text.length > 0) {
    // something
}

といった、メソッドを呼び出す前にクラスやセレクタを確認することによって例外を防ぐのですが、いちいち確認するのは煩雑過ぎます。
通常は上記の様に呼び出し毎に確認するのではなく、モデル等の初期化で確認してその後安全に値にアクセスする方法を取ると思うのですが、やはり確認するコードは煩雑になります。

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

##各種対処方法

###1. NSNullをnilとして振る舞わせる
NullSafe

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

###2. NSNullを一括で削除する
ISRemoveNull

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

NSJSONSerialization-NSNullRemoval

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

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

###3. NSNullを効率よく削除する
CollectionUtils

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

詳しい解説
NSArrayやNSDictionaryからNSNullを効率よく取り除く

###4. NSNullの場合にnilを返す

実装例
// Categoryにnilを返すメソッドを追加
@implementation NSDictionary (Additions)

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

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

##結論
NSNullを削除したければ3のCollectionUtilsが現状だと最適だと思いました。AFNetworking 2.xのresponseSerializerにも対応したライブラリも公開されているので導入もめちゃめちゃ楽です。

CollectionUtils-AFNetworking
manager.responseSerializer = [CUJSONResponseSerializer serializer];

NSNullを残しておきたい場合は、4を使用するのもいいと思います。

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

JSONKitでもこことかここで話題になっていますが対応はされていません。何か問題あるのかな。(※リンク先では空文字列を返せとわけわかんないこと言ってますが)

##参考
JavaScript Object Notation - wikipedia
JSONにNSNullが入ってきたとき
NSArrayやNSDictionaryからNSNullを効率よく取り除く

79
80
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
79
80

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?