web-dev-qa-db-ja.com

NSManagedObjectプロパティ値のNSNull処理

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]を処理するためのより良い方法はありますか、それともこれを完全に異なるがより簡単にする方法はありますか?

35
Lucien

これをマクロでラップすると、少し簡単になるかもしれません。

#define NULL_TO_NIL(obj) ({ __typeof__ (obj) __obj = (obj); __obj == [NSNull null] ? nil : obj; })

次に、次のようなことを書くことができます

fight.winnerID = NULL_TO_NIL([dict objectForKey:@"winner"]);

または、辞書を前処理して、すべてのNSNullsをnilに置き換えてから、管理対象オブジェクトに詰め込むこともできます。

65
Lily Ballard

さて、私は今朝、良い解決策で目が覚めました。これはどうですか:

可変配列と辞書を受け取るオプションを使用して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
8
Lucien

使用する前に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オブジェクトを省略する方が望ましいです。

1
Nick Lockwood

私は同じ問題で立ち往生していて、この投稿を見つけて、少し異なる方法でそれをしました。

「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"];

それでおしまい

0
user3034970

別の方法は

-[NSObject setValuesForKeysWithDictionary:]

このシナリオでは、次のことができます

[fight setValuesForKeysWithDictionary:dict];

ヘッダーNSKeyValueCoding.hで、「​​値がNSNullであるディクショナリエントリの結果は-setValue:nil forKey:key受信者に送信されるメッセージ。

唯一の欠点は、辞書内のすべてのキーをレシーバー内にあるキーに変換する必要があることです。つまり.

dict[@"winnerID"] = dict[@"winner"];
[dict removeObjectForKey:@"winner"];
0
Damien Pontifex