コアデータオブジェクトグラフ(2対多の関係でリンクされた2つのエンティティで構成されています)があります。
比較的経験の浅いiPhone開発者として、だれかがアプローチを推奨できるかどうか、そしてiPhoneに適したJSON実装を好むかどうかに興味がありました。
エンティティ間の関係を維持しながら、コアデータレコードをJSON文字列に変換します。そして
jSONストリングをコアデータオブジェクトに変換します(再びエンティティ間の関係を保持します)。
この点に関するチュートリアル/コードサンプルを検索しましたが、失敗しましたので、ご支援をいただければ幸いです。
最初に、使用するJSONライブラリを選択します。私は個人的にTouchJSONが好きですが、そこにある他のいくつかもかなりいいです。複雑な部分は、それほど難しくはありませんが、管理対象オブジェクトを変換に適した構造に変換することです。私はこれを実際に素早く書いたので、エラーが1つか2つあるかもしれません:)
呼び出すメソッドは次のとおりです。
- (NSString*)jsonStructureFromManagedObjects:(NSArray*)managedObjects;
- (NSArray*)managedObjectsFromJSONStructure:(NSString*)json withManagedObjectContext:(NSManagedObjectContext*)moc;
また、実装は次のとおりです。
- (NSDictionary*)dataStructureFromManagedObject:(NSManagedObject*)managedObject
{
NSDictionary *attributesByName = [[managedObject entity] attributesByName];
NSDictionary *relationshipsByName = [[managedObject entity] relationshipsByName];
NSMutableDictionary *valuesDictionary = [[managedObject dictionaryWithValuesForKeys:[attributesByName allKeys]] mutableCopy];
[valuesDictionary setObject:[[managedObject entity] name] forKey:@"ManagedObjectName"];
for (NSString *relationshipName in [relationshipsByName allKeys]) {
NSRelationshipDescription *description = [[[managedObject entity] relationshipsByName] objectForKey:relationshipName];
if (![description isToMany]) {
NSManagedObject *relationshipObject = [managedObject valueForKey:relationshipName];
[valuesDictionary setObject:[self dataStructureForManagedObject:relationshipObject] forKey:relationshipName];
continue;
}
NSSet *relationshipObjects = [managedObject objectForKey:relationshipName];
NSMutableArray *relationshipArray = [[NSMutableArray alloc] init];
for (NSManagedObject *relationshipObject in relationshipObjects) {
[relationshipArray addObject:[self dataStructureForManagedObject:relationshipObject]];
}
[valuesDictionary setObject:relationshipArray forKey:relationshipName];
}
return [valuesDictionary autorelease];
}
- (NSArray*)dataStructuresFromManagedObjects:(NSArray*)managedObjects
{
NSMutableArray *dataArray = [[NSMutableArray alloc] init];
for (NSManagedObject *managedObject in managedObjects) {
[dataArray addObject:[self dataStructureForManagedObject:managedObject]];
}
return [dataArray autorelease];
}
- (NSString*)jsonStructureFromManagedObjects:(NSArray*)managedObjects
{
NSArray *objectsArray = [self dataStructuresFromManagedObjects:managedObjects];
NSString *jsonString = [[CJSONSerializer serializer] serializeArray:objectsArray];
return jsonString;
}
- (NSManagedObject*)managedObjectFromStructure:(NSDictionary*)structureDictionary withManagedObjectContext:(NSManagedObjectContext*)moc
{
NSString *objectName = [structureDictionary objectForKey:@"ManagedObjectName"];
NSManagedObject *managedObject = [NSEntityDescription insertNewObjectForEntityForName:objectName inManagedObjectContext:moc];
[managedObject setValuesForKeysWithDictionary:structureDictionary];
for (NSString *relationshipName in [[[managedObject entity] relationshipsByName] allKeys]) {
NSRelationshipDescription *description = [relationshipsByName objectForKey:relationshipName];
if (![description isToMany]) {
NSDictionary *childStructureDictionary = [structureDictionary objectForKey:relationshipName];
NSManagedObject *childObject = [self managedObjectFromStructure:childStructureDictionary withManagedObjectContext:moc];
[managedObject setObject:childObject forKey:relationshipName];
continue;
}
NSMutableSet *relationshipSet = [managedObject mutableSetForKey:relationshipName];
NSArray *relationshipArray = [structureDictionary objectForKey:relationshipName];
for (NSDictionary *childStructureDictionary in relationshipArray) {
NSManagedObject *childObject = [self managedObjectFromStructure:childStructureDictionary withManagedObjectContext:moc];
[relationshipSet addObject:childObject];
}
}
return managedObject;
}
- (NSArray*)managedObjectsFromJSONStructure:(NSString*)json withManagedObjectContext:(NSManagedObjectContext*)moc
{
NSError *error = nil;
NSArray *structureArray = [[CJSONDeserializer deserializer] deserializeAsArray:json error:&error];
NSAssert2(error == nil, @"Failed to deserialize\n%@\n%@", [error localizedDescription], json);
NSMutableArray *objectArray = [[NSMutableArray alloc] init];
for (NSDictionary *structureDictionary in structureArray) {
[objectArray addObject:[self managedObjectFromStructure:structureDictionary withManagedObjectContext:moc]];
}
return [objectArray autorelease];
}
これは再帰的であるため、注意しなければ、永続ストア全体を簡単に翻訳できます。あなたの関係を見て、あなたが翻訳したいオブジェクトだけを取得するように、それらがオブジェクトツリーを「下る」だけであることを確認してください。
コードのクラッシュを引き起こした小さなタイプミスを指摘したかったのですが、これで数分節約できることを願っています。
- (NSArray*)dataStructuresFromManagedObjects:(NSArray*)managedObjects {
NSMutableArray *dataArray = [[NSArray alloc] init];
for (NSManagedObject *managedObject in managedObjects) {
[dataArray addObject:[self dataStructureFromManagedObject:managedObject]];
}
return [dataArray autorelease];
}
NSMutableArray *dataArray = [[NSArray alloc] init]; // This should be NSMutableArray
本当にNSMutableArray *dataArray = [[NSMutableArray alloc] init];
それがすべてです。
ありがとうございました
コアデータとRailsの同期 は、コアデータオブジェクトをJSONとの間でシリアル化/逆シリアル化するためのサンプルコードを含む詳細なプレゼンテーションです(コアデータパーツのスライド55にスキップします)。彼のサンプルコードでは、リレーションシップのないかなり単純なモデルを想定していますが、拡張はかなり簡単だと思います。
プレゼンテーションでは、コアデータモデルをRESTベースのWebアプリケーションと同期させるための詳細についても説明します。 ObjectiveResource や ASIHTTPRequest などの便利なライブラリへのポインターを使用します。それがあなたがやろうとしていることであるかどうかはわかりませんが、Core Dataのコードを見ても価値があります。
上記のコメントの1つで説明したように、管理オブジェクトにNSDate
がある場合、NSDate
を含むオブジェクトのシリアル化で問題が発生します。簡単な修正方法は、objective-cカテゴリを使用してJSONDataRepresentation
メソッドをNSDate
に追加することです。
これらの2つのファイルをプロジェクトに追加します。
NSdate.h:
#import <Foundation/Foundation.h>
@interface NSDate (jsondatarepresentation)
- (NSData*) JSONDataRepresentation;
@end
NSDate.m:
#import "NSDate.h"
@implementation NSDate (jsondatarepresentation)
- (NSData*) JSONDataRepresentation {
return [[[NSNumber numberWithDouble:[self timeIntervalSince1970]] stringValue] dataUsingEncoding:NSUTF8StringEncoding];
}
@end
Idがこの質問の簡単な更新を投稿すると思いました。私はMarcusとBrandonのAnswersに従い、JSONエクスポートのためにこれを思いつきました(TouchJSONをまだ使用しています):
- (NSData*)jsonStructureFromManagedObjects:(NSArray*)managedObjects
{
NSArray *objectsArray = [self dataStructuresFromManagedObjects:managedObjects];
NSData *jsonData = [[CJSONSerializer serializer] serializeArray:objectsArray error:nil];
return jsonData;
}
- (NSArray*)dataStructuresFromManagedObjects:(NSArray*)managedObjects
{
NSMutableArray *dataArray = [[NSMutableArray alloc] init];
for (NSManagedObject *managedObject in managedObjects) {
[dataArray addObject:[self dataStructureFromManagedObject:managedObject]];
}
return dataArray;
}
- (NSDictionary*)dataStructureFromManagedObject:(NSManagedObject*)managedObject
{
NSDictionary *attributesByName = [[managedObject entity] attributesByName];
NSDictionary *relationshipsByName = [[managedObject entity] relationshipsByName];
NSMutableDictionary *valuesDictionary = [[managedObject dictionaryWithValuesForKeys:[attributesByName allKeys]] mutableCopy];
[valuesDictionary setObject:[[managedObject entity] name] forKey:@"ManagedObjectName"];
for (NSString *relationshipName in [relationshipsByName allKeys]) {
NSRelationshipDescription *description = [[[managedObject entity] relationshipsByName] objectForKey:relationshipName];
if ([[[description userInfo] objectForKey:@"isExportable"] boolValue] == YES) {
if (![description isToMany]) {
NSManagedObject *relationshipObject = [managedObject valueForKey:relationshipName];
if (relationshipObject) {
[valuesDictionary setObject:[self dataStructureFromManagedObject:relationshipObject] forKey:relationshipName];
}
continue;
}
NSSet *relationshipObjects = [managedObject valueForKey:relationshipName];
NSMutableArray *relationshipArray = [[NSMutableArray alloc] init];
for (NSManagedObject *relationshipObject in relationshipObjects) {
[relationshipArray addObject:[self dataStructureFromManagedObject:relationshipObject]];
}
[valuesDictionary setObject:relationshipArray forKey:relationshipName];
}
}
return valuesDictionary;
}
インポートを機能させることができませんでしたが、おそらくMagical Recordを使用しているという事実と関係があるかもしれませんが、私は着信JSONストリームをループしてオブジェクトを手動で作成しています...
JSON同期を行うライブラリがあります: https://github.com/sixdegrees/lidenbrock
この投稿に出くわしましたが、これは非常に効果的です。
http://touchalicious.com/blog/2009/10/25/turn-core-data-models-into-json.html
これは再帰的であるため、多対多の関係はループし続けます。これを避けるために、コアデータモデルの関係のユーザー情報辞書に「isExportable」キーを追加しました。次に、このキーを確認し、それなしで関係をループしないように選択できます。
if ([property isKindOfClass:[NSRelationshipDescription class]])
{
NSRelationshipDescription *relationshipDescription = (NSRelationshipDescription *)property;
if ([[[relationshipDescription userInfo] objectForKey:@"isExportable"] boolValue] == YES)
{
NSString *name = [relationshipDescription name];
if ([relationshipDescription isToMany])
{
NSMutableArray *arr = [properties valueForKey:name];
if (!arr)
{
arr = [[NSMutableArray alloc] init];
[properties setValue:arr forKey:name];
}
for (NSManagedObject *o in [self mutableSetValueForKey:name])
{
[arr addObject:[o propertiesDictionary]];
}
}
else
{
NSManagedObject *o = [self valueForKey:name];
[properties setValue:[o propertiesDictionary] forKey:name];
}
}
}
}
マーカス・S・ザラは、私にインスピレーションを与えて、実用的なバージョンに再帰的なアイデアをもたらしました。このバージョンでは、CoreDataにキーを設定する必要はなく、プロジェクトでキーをカットアンドペーストできます:-)
// MARK: - encoding and decoding CoreData entity to dictionary
func dataStructureFromManagedObject( managedObject:NSManagedObject?, parentEntity: NSEntityDescription? = nil) -> NSMutableDictionary {
if (managedObject != nil) {
var attributesByName: NSDictionary = managedObject!.entity.attributesByName
var relationshipsByName: NSDictionary = managedObject!.entity.relationshipsByName
var valuesImmutableDictionary: NSDictionary = managedObject!.dictionaryWithValuesForKeys( attributesByName.allKeys)
var valuesDictionary: NSMutableDictionary = valuesImmutableDictionary.mutableCopy() as NSMutableDictionary
valuesDictionary.setObject( managedObject!.entity.name!, forKey: "ManagedObjectName")
for relationshipNameObject in relationshipsByName.allKeys {
var relationshipName: NSString = relationshipNameObject as NSString
var relationshipDescription: NSRelationshipDescription? = relationshipsByName.objectForKey( relationshipName) as? NSRelationshipDescription
if !relationshipDescription!.toMany {
// ono to one
if parentEntity == nil || (relationshipDescription! as NSRelationshipDescription).destinationEntity != parentEntity! {
// no parent or relationship is "downward" -> object for relationship must be added
var relationshipObject: NSManagedObject? = managedObject!.valueForKey( relationshipName) as? NSManagedObject
var relationshipObjectDictionary: NSMutableDictionary = self.dataStructureFromManagedObject( relationshipObject, parentEntity: managedObject?.entity)
valuesDictionary.setObject( relationshipObjectDictionary, forKey: relationshipName)
} else {
// relationship is "upward" -> nothing to do
}
} else {
// one to many -> all objects must be added
var relationshipObjects: NSSet = managedObject!.mutableSetValueForKey( relationshipName)
var relationshipArray:NSMutableArray = []
for relationshipObjectRaw in relationshipObjects {
var relationshipObject:NSManagedObject? = relationshipObjectRaw as? NSManagedObject
if relationshipObject != nil && !relationshipObject!.entity.isKindOfEntity( managedObject!.entity) {
relationshipArray.addObject(self.dataStructureFromManagedObject( relationshipObject, parentEntity: managedObject?.entity))
}
}
valuesDictionary.setObject( relationshipArray, forKey: relationshipName)
}
}
return valuesDictionary
} else {
return NSMutableDictionary()
}
}
func managedObjectFromStructure( structureDictionary: NSDictionary, moc: NSManagedObjectContext, parentObject: NSManagedObject? = nil) -> NSManagedObject {
if structureDictionary.count > 0 {
var objectName:NSString = structureDictionary.objectForKey( "ManagedObjectName") as NSString
var managedObject:NSManagedObject = NSEntityDescription.insertNewObjectForEntityForName( objectName, inManagedObjectContext: moc) as NSManagedObject
var relationshipsByName: NSDictionary = managedObject.entity.relationshipsByName
var realObjectStructure:NSMutableDictionary = structureDictionary.mutableCopy() as NSMutableDictionary
realObjectStructure.removeObjectForKey( "ManagedObjectName")
for key in realObjectStructure.allKeys {
// search for "ManagedObjectName" relationship entrys and delete them before filling the managedObject from this structure
for relationshipName in relationshipsByName.allKeys {
if relationshipName as NSString == key as NSString {
realObjectStructure.removeObjectForKey( key)
}
}
}
managedObject.setValuesForKeysWithDictionary( realObjectStructure)
// the main object with attributes is created. Now care about the relationships
for relationshipName in managedObject.entity.relationshipsByName.keys {
var description:NSRelationshipDescription = relationshipsByName.objectForKey( relationshipName) as NSRelationshipDescription
if !description.toMany {
// to one relationship
if parentObject == nil || description.destinationEntity != parentObject!.entity {
// no parent or relationship is "downward" -> recurse structure to add
var childStructureDictionary:NSDictionary = structureDictionary.objectForKey( relationshipName) as NSDictionary
if childStructureDictionary.count > 0 {
// dictionary not empty -> object must be created and added
var childObject:NSManagedObject? = self.managedObjectFromStructure( childStructureDictionary, moc: moc, parentObject: managedObject)
// validateForUpdate
var error:NSError?
if !managedObject.validateForUpdate( &error) {
println("Error: Object not in valid state for update!!! -> \(error)")
} else {
managedObject.setValue( childObject, forKey: relationshipName as NSString)
}
} else {
// relationship is "upward" -> nothing to do
}
}
} else {
// to many relationship
var relationshipSet:NSMutableSet = managedObject.mutableSetValueForKey( relationshipName as NSString)
var relationshipArray:NSArray = structureDictionary.objectForKey( relationshipName as NSString) as NSArray
for childStructureDictionary in relationshipArray {
if childStructureDictionary.count > 0 {
// dictionary not empty -> object must be created and added
var childObject:NSManagedObject = self.managedObjectFromStructure( childStructureDictionary as NSDictionary, moc: moc, parentObject: managedObject)
// validateForUpdate
var error:NSError?
if !managedObject.validateForUpdate( &error) {
println( "Error: Object not in valid state for update!!! -> \(error)")
} else {
relationshipSet.addObject( childObject)
}
} else {
// no object was behind the relationship -> nothing to do
}
}
// save set
managedObject.setValue( relationshipSet, forKey: relationshipName as NSString)
}
}
// final check validateForUpdate
var error:NSError?
if !managedObject.validateForUpdate( &error) {
println( "Error: Object not in valid state for update although all previous check are passed!!! -> \(error)")
}
return managedObject
} else {
println( "Error: structure for object was empty. this should not happen at this point")
var objectName:NSString = structureDictionary.objectForKey( "ManagedObjectName") as NSString
var managedObject:NSManagedObject = NSEntityDescription.insertNewObjectForEntityForName( objectName, inManagedObjectContext: moc) as NSManagedObject
return managedObject
}
}
func dataStructuresFromManagedObjects( managedObjects: NSArray) -> NSArray {
var dataArray:NSMutableArray = []
for managedObject in managedObjects {
dataArray.addObject( self.dataStructureFromManagedObject(managedObject as? NSManagedObject))
}
return dataArray
}
ここで重要なのは、再帰への引数として親エンティティを渡すことです。そのため、データで満たすべき関係を決定できます。したがって、両方の関数:dataStructureFromManagedObject
とmanagedObjectFromStructure
は、CoreDataのエンティティオブジェクトを辞書にエンコードおよびデコードし、オブジェクトに戻すことができます。