NSManagedObject
のプロパティの値を設定しています。これらの値は、JSONファイルから適切にシリアル化されたNSDictionary
から取得されます。私の問題は、ある値が[NSNull null]
の場合、プロパティに直接割り当てることができないということです。
fight.winnerID = [dict objectForKey:@"winner"];
これはNSInvalidArgumentException
をスローします
"winnerID"; desired type = NSString; given type = NSNull; value = <null>;
[NSNull null]
の値を簡単に確認し、代わりにnil
を割り当てることができます。
fight.winnerID = [dict objectForKey:@"winner"] == [NSNull null] ? nil : [dict objectForKey:@"winner"];
しかし、これはエレガントではなく、設定するプロパティがたくさんあると面倒になると思います。
また、これはNSNumber
プロパティを処理するときに難しくなります。
fight.round = [NSNumber numberWithUnsignedInteger:[[dict valueForKey:@"round"] unsignedIntegerValue]]
NSInvalidArgumentException
は次のようになりました。
[NSNull unsignedIntegerValue]: unrecognized selector sent to instance
この場合、NSUInteger
の値を作成する前に[dict valueForKey:@"round"]
を処理する必要があります。そして、1行のソリューションはなくなりました。
@try @catchブロックを作成しようとしましたが、最初の値がキャッチされるとすぐに、@ tryブロック全体がジャンプし、次のプロパティは無視されます。
[NSNull null]
を処理するためのより良い方法はありますか、それともこれを完全に異なるがより簡単にする方法はありますか?
これをマクロでラップすると、少し簡単になるかもしれません。
#define NULL_TO_NIL(obj) ({ __typeof__ (obj) __obj = (obj); __obj == [NSNull null] ? nil : obj; })
次に、次のようなことを書くことができます
fight.winnerID = NULL_TO_NIL([dict objectForKey:@"winner"]);
または、辞書を前処理して、すべてのNSNull
sをnil
に置き換えてから、管理対象オブジェクトに詰め込むこともできます。
さて、私は今朝、良い解決策で目が覚めました。これはどうですか:
可変配列と辞書を受け取るオプションを使用してJSONをシリアル化します。
NSMutableDictionary *rootDict = [NSJSONSerialization JSONObjectWithData:_receivedData options:NSJSONReadingMutableContainers error:&error];
...
LeafDictから[NSNull null]
値を持つキーのセットを取得します。
NSSet *nullSet = [leafDict keysOfEntriesWithOptions:NSEnumerationConcurrent passingTest:^BOOL(id key, id obj, BOOL *stop) {
return [obj isEqual:[NSNull null]] ? YES : NO;
}];
フィルタリングされたプロパティをMutableleafDictから削除します。
[leafDict removeObjectsForKeys:[nullSet allObjects]];
これで、fight.winnerID = [dict objectForKey:@"winner"];
を呼び出すと、winnerIDは(null)
または<null>
ではなく、自動的に[NSNull null]
またはnil
になります。
これとは関係ありませんが、文字列をNSNumberに解析するときは、NSNumberFormatter
を使用する方がよいことに気付きました。これは、nil文字列からintegerValue
を取得する方法でした。 0
の望ましくないNSNumber、実際にはゼロにしたかったとき。
前:
// when [leafDict valueForKey:@"round"] == nil
fight.round = [NSNumber numberWithInteger:[[leafDict valueForKey:@"round"] integerValue]]
// Result: fight.round = 0
後:
__autoreleasing NSNumberFormatter* numberFormatter = [[NSNumberFormatter alloc] init];
fight.round = [numberFormatter numberFromString:[leafDict valueForKey:@"round"]];
// Result: fight.round = nil
使用する前にJSONで生成された辞書または配列からnullを取り除くために、いくつかのカテゴリメソッドを作成しました。
@implementation NSMutableArray (StripNulls)
- (void)stripNullValues
{
for (int i = [self count] - 1; i >= 0; i--)
{
id value = [self objectAtIndex:i];
if (value == [NSNull null])
{
[self removeObjectAtIndex:i];
}
else if ([value isKindOfClass:[NSArray class]] ||
[value isKindOfClass:[NSDictionary class]])
{
if (![value respondsToSelector:@selector(setObject:forKey:)] &&
![value respondsToSelector:@selector(addObject:)])
{
value = [value mutableCopy];
[self replaceObjectAtIndex:i withObject:value];
}
[value stripNullValues];
}
}
}
@end
@implementation NSMutableDictionary (StripNulls)
- (void)stripNullValues
{
for (NSString *key in [self allKeys])
{
id value = [self objectForKey:key];
if (value == [NSNull null])
{
[self removeObjectForKey:key];
}
else if ([value isKindOfClass:[NSArray class]] ||
[value isKindOfClass:[NSDictionary class]])
{
if (![value respondsToSelector:@selector(setObject:forKey:)] &&
![value respondsToSelector:@selector(addObject:)])
{
value = [value mutableCopy];
[self setObject:value forKey:key];
}
[value stripNullValues];
}
}
}
@end
標準のJSON解析ライブラリにデフォルトでこの動作があると便利です。ほとんどの場合、NSNullとして含めるよりもnullオブジェクトを省略する方が望ましいです。
私は同じ問題で立ち往生していて、この投稿を見つけて、少し異なる方法でそれをしました。
「NSDictionary」の新しいカテゴリファイルを作成し、この1つのメソッドを追加します-
@implementation NSDictionary (SuperExtras)
- (id)objectForKey_NoNSNULL:(id)aKey
{
id result = [self objectForKey:aKey];
if(result==[NSNull null])
{
return nil;
}
return result;
}
@end
後でコードで使用するために、NSNULLを含めることができるプロパティの場合は、このように使用します-
newUser.email = [loopdict objectForKey_NoNSNULL:@"email"];
それでおしまい
別の方法は
-[NSObject setValuesForKeysWithDictionary:]
このシナリオでは、次のことができます
[fight setValuesForKeysWithDictionary:dict];
ヘッダーNSKeyValueCoding.hで、「値がNSNullであるディクショナリエントリの結果は-setValue:nil forKey:key
受信者に送信されるメッセージ。
唯一の欠点は、辞書内のすべてのキーをレシーバー内にあるキーに変換する必要があることです。つまり.
dict[@"winnerID"] = dict[@"winner"];
[dict removeObjectForKey:@"winner"];