JSON を Web サービスから Core Data に解析しようとしています。RSAPI ライブラリを調べましたが、私の場合の使用方法がよくわかりません。今までJSONデータを取得するために使用しjson = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error]; NSData *data = [NSData dataWithContentsOfURL:url];
ていたのは、次のとおりです。そして、それをテーブルビューに配列として表示します。JSON データをカスタム エンティティの Core Data に保存する方法について何か提案はありますか?
1 に答える
簡単で危険な方法
Cocoa は、これをはるかに簡単にする方法を提供します。キー値コーディングのおかげで、上記を 1 行に減らす NSObject のメソッドがあります。
[myObj setValuesForKeysWithDictionary:jsonDict];
これは、jsonDict のキーと値のペアを介して実行され、同じキーと値のペアを myObj に設定します。唯一の注意点は、キー名が一致している必要があることですが、データ モデルで同じ名前を付けていれば問題ありません。
実は、もう 1 つ注意点があります。このメソッドでは、ディクショナリに含まれるキーによって、管理対象オブジェクトで使用されるキーが決まります。しかし、エンティティの説明の一部ではないキーがディクショナリに含まれている場合はどうなるでしょうか。次に、オブジェクトがキーのキー値コーディングに準拠していないことを示すエラーでアプリがクラッシュします。ここでこの方法を使用すると、使用している Web サービスにアプリの安定性が左右されます。Web サービスを完全に制御できない場合は、この方法を使用すると重大なリスクが生じます。もっとうまくやることができます。
管理対象オブジェクトの検査
Core Data を使用しているため、NSEntityDescription を使用して管理対象オブジェクトの属性を検査し、上記のロジックを逆にすることができます。どのキーと値のペアを使用するかを決定するために受信ディクショナリを使用する代わりに、マネージド オブジェクトを使用できます。
エンティティを要求することで、管理対象オブジェクトのエンティティの説明を検索できます。これにより、存在する属性など、あらゆる種類の有用な情報を要求できる NSEntityDescription が得られます。これにより、JSON から変換する際のより安全なアプローチにつながります。
NSDictionary *attributes = [[myObj entity] attributesByName];
for (NSString *attribute in attributes) {
id value = [jsonDict objectForKey:attribute];
if (value == nil) {
continue;
}
[myObj setValue:value forKey:attribute];
}
このコードは、エンティティの属性を繰り返し処理し、ディクショナリで各属性のキーを検索し、そのキーと値のペアを管理対象オブジェクトに適用します。これは依然として優れた一般的なものであり、受信データを変更してもアプリがクラッシュしなくなるという利点があります。エンティティの説明に実際に存在する属性の値のみをディクショナリで検索します。余分な辞書キーは単純に無視されます。
コードがエンティティのすべての属性にヒットするため、nil のチェックが行われます。受信データに存在しない属性 (「お気に入り」フラグなど) がエンティティにある場合、コードはその属性に nil 値を設定することになります。これにより、本当に保持したいデータが誤って消去される可能性があります。
壊れていて一貫性のない JSON の処理
JSON 標準では、文字列と数字を区別する方法が非常に明確です。基本的に、文字列は引用符で囲まれ、数字は囲まれていません。ただし、JSON Web サービスは、この要件に従うことが常に適切とは限りません。そして、そうであったとしても、あるレコードから別のレコードへと常に一貫しているわけではありません。
私が最近遭遇したものと同様の例: 衣類のサイズ情報。サイズは Web サービスによって提供され、通常は「30-32」、「34-36」などです。これらは JSON で文字列として正しく引用され、アプリはそれらを文字列属性として保存し、ユーザーに表示します。そのまま。
ただし、「8」、「10」など、サイズに 1 つの数字しかない場合もあります。この場合、サーバーは引用符を削除して数字にします。私の JSON パーサーは正しく NSNumber を生成します。これをエンティティの文字列属性に保存したいだけです! Objective-C のイントロスペクションを使用して、JSON パーサーから NSString または NSNumber を受け取ったかどうかを確認できますが、管理対象オブジェクトがプロパティに期待する型も知る必要があります。JSON パーサーを壊して、常に NSString を返すようにすることを簡単に考えました。そうすれば、少なくとも何を期待できるかがわかります。幸いなことに、NSEntityDescription が再び役に立ちました。
エンティティの説明にその属性名を尋ねるだけでなく、Core Data モデルで構成されている属性の種類についても問い合わせることができます。これは NSAttributeType として返されます。これを使用して、上記のコードを拡張して、一致しないデータ型を処理できます。簡単に再利用できるようにコードを抽象化することもおそらく良い考えなので、NSManagedObject のカテゴリに入れます。
@implementation NSManagedObject (safeSetValuesKeysWithDictionary)
- (void)safeSetValuesForKeysWithDictionary:(NSDictionary *)keyedValues
{
NSDictionary *attributes = [[self entity] attributesByName];
for (NSString *attribute in attributes) {
id value = [keyedValues objectForKey:attribute];
if (value == nil) {
// Don't attempt to set nil, or you'll overwite values in self that aren't present in keyedValues
continue;
}
NSAttributeType attributeType = [[attributes objectForKey:attribute] attributeType];
if ((attributeType == NSStringAttributeType) && ([value isKindOfClass:[NSNumber class]])) {
value = [value stringValue];
} else if (((attributeType == NSInteger16AttributeType) || (attributeType == NSInteger32AttributeType) || (attributeType == NSInteger64AttributeType) || (attributeType == NSBooleanAttributeType)) && ([value isKindOfClass:[NSString class]])) {
value = [NSNumber numberWithInteger:[value integerValue]];
} else if ((attributeType == NSFloatAttributeType) && ([value isKindOfClass:[NSString class]])) {
value = [NSNumber numberWithDouble:[value doubleValue]];
}
[self setValue:value forKey:attribute];
}
}
@end
このコードは、着信 JSON から作成したディクショナリで見つかった値と管理対象オブジェクトの属性の両方の型を検査し、文字列/数値の不一致がある場合は値を変更して、期待されるものと一致することを確認します。
これはかなり便利になっています。JSON から NSManagedObject への一般的な変換であるだけでなく、エンティティ固有の情報を必要とせずに、数値型と文字列型の間の不一致も処理します。しかし、それはさらに良いかもしれません。取扱日
JSON には日付型はありませんが、JSON では日付が一般的です。それらは、何らかの日付形式を使用して文字列として表されているだけです。おそらく、文字列ではなく NSDate が必要な場合があります。NSDateFormatter はここで非常に便利ですが、日付変換も一般的なものにするとよいので、日付属性を特殊なケースにする必要はありませんか? 私がこれでどこに行くのか分かりますか?
上記のカテゴリ メソッドを記述したので、オプションの NSDateFormatter 引数を追加して、エンティティの属性が日付を予期するたびにそれを使用するのはそれほど面倒ではありません。この変更されたバージョンは、その引数と、「else … if」チェーンに追加のケースを追加します。
@implementation NSManagedObject (safeSetValuesKeysWithDictionary)
- (void)safeSetValuesForKeysWithDictionary:(NSDictionary *)keyedValues dateFormatter:(NSDateFormatter *)dateFormatter
{
NSDictionary *attributes = [[self entity] attributesByName];
for (NSString *attribute in attributes) {
id value = [keyedValues objectForKey:attribute];
if (value == nil) {
continue;
}
NSAttributeType attributeType = [[attributes objectForKey:attribute] attributeType];
if ((attributeType == NSStringAttributeType) && ([value isKindOfClass:[NSNumber class]])) {
value = [value stringValue];
} else if (((attributeType == NSInteger16AttributeType) || (attributeType == NSInteger32AttributeType) || (attributeType == NSInteger64AttributeType) || (attributeType == NSBooleanAttributeType)) && ([value isKindOfClass:[NSString class]])) {
value = [NSNumber numberWithInteger:[value integerValue]];
} else if ((attributeType == NSFloatAttributeType) && ([value isKindOfClass:[NSString class]])) {
value = [NSNumber numberWithDouble:[value doubleValue]];
} else if ((attributeType == NSDateAttributeType) && ([value isKindOfClass:[NSString class]]) && (dateFormatter != nil)) {
value = [dateFormatter dateFromString:value];
}
[self setValue:value forKey:attribute];
}
}
@end