JSON dan Data Inti di iPhone

93

Saya memiliki grafik objek data inti (terdiri dari dua entitas yang dihubungkan oleh hubungan ke banyak).

Saya penasaran, sebagai pengembang iPhone yang relatif tidak berpengalaman, apakah ada yang dapat merekomendasikan pendekatan, dan implementasi JSON yang sesuai untuk iPhone, yang memungkinkan saya untuk:

  1. mengonversi catatan data inti menjadi string JSON (sambil mempertahankan hubungan antara entitas); dan

  2. mengonversi string JSON kembali menjadi objek data inti (sekali lagi mempertahankan hubungan antar entitas).

Saya telah mencari, tidak berhasil, untuk contoh tutorial / kode tentang hal ini sehingga bantuan apa pun akan kami terima dengan penuh syukur.

Urizen
sumber
3
Bagi siapa pun yang mencari ini untuk iOS5, sekarang ada NSJSONSerialization developer.apple.com/library/mac/#documentation/Foundation/… stackoverflow.com/questions/6726899/nsjsonserialization-in-ios5
nicerobot
Saya tahu bahwa pertanyaan ini agak lama, tetapi saya telah membuat perpustakaan sederhana yang disebut OSReflectionKit , yang memungkinkan Anda untuk membuat serial / deserialisasi objek ke / dari JSON, menggunakan NSJSONSerialization , atau NSDictionary. Ini juga mendukung objek Data Inti.
Alexandre OS

Jawaban:

103

Pertama, pilih perpustakaan JSON untuk digunakan, saya pribadi suka TouchJSON tetapi beberapa lainnya di luar sana juga cukup bagus. Bagian yang rumit, meskipun tidak terlalu sulit, adalah mengubah objek yang Anda kelola menjadi struktur yang sesuai untuk konversi. Saya menulis ini dengan sangat cepat sehingga mungkin ada satu atau dua kesalahan :)

Metode yang Anda panggil adalah:

- (NSString*)jsonStructureFromManagedObjects:(NSArray*)managedObjects;
- (NSArray*)managedObjectsFromJSONStructure:(NSString*)json withManagedObjectContext:(NSManagedObjectContext*)moc;

Dan implementasinya adalah sebagai berikut:

- (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];
}

Sekarang ini rekursif sehingga Anda dapat dengan mudah menerjemahkan seluruh penyimpanan persisten Anda jika Anda tidak berhati-hati. Perhatikan hubungan Anda dan pastikan bahwa mereka hanya "turun" dari pohon objek sehingga Anda hanya mendapatkan objek yang ingin diterjemahkan.

Marcus S. Zarra
sumber
Sekali lagi terima kasih atas jawaban bagus lainnya dan untuk buku Anda yang sangat membantu! :)
Urizen
2
Hai Marcus. Saya baru saja mencoba kode di atas (dengan beberapa perubahan kecil untuk membuatnya dikompilasi dan eksekusi tampaknya berjalan tanpa batas hingga aplikasi mogok). Maaf mengganggu Anda, tetapi saya ingin tahu apakah Anda mungkin dapat mengarahkan saya ke arah yang benar untuk menyelesaikan masalah ini. Tampaknya terjadi dengan rekursi dalam metode datastructureFromManagedObject ...
Urizen
1
Tergantung pada struktur data Anda. Jika model Anda akan menghasilkan loop, maka model tersebut akan berjalan selamanya. Tinjau model data Anda dan pastikan itu adalah desain pohon atau hentikan logika dalam kode rekursif untuk mencegah perulangan.
Marcus S. Zarra
1
Apakah Anda benar-benar mencoba menjalankan kode ini? Ada banyak sekali kesalahan. dataStructureForManagedObject bahkan tidak ada. Saya pikir itu mungkin hanya salah ketik tetapi jika Anda mengubahnya menjadi dataStructureFromManagedObject semuanya hanya mengulang tanpa batas memantul kembali di antara pasangan hubungan. Apakah saya melewatkan beberapa kode tambahan di sini?
Chris Mitchelmore
1
Contoh kode ini ditulis di browser dua tahun lalu. Hal itu dimaksudkan agar menginspirasi bukan menjadi copy paste. Sedangkan untuk infinite loop, itu berarti Anda memiliki loop dalam model Anda dan Anda kemudian perlu menambahkan logika khusus model ke aplikasi Anda untuk memutus siklus. Ada beberapa cara untuk melakukannya yang tidak ada dalam contoh ini.
Marcus S. Zarra
12

Saya hanya ingin menunjukkan kesalahan ketik kecil, yang menyebabkan kode macet, dan mudah-mudahan ini akan menghemat beberapa menit.

- (NSArray*)dataStructuresFromManagedObjects:(NSArray*)managedObjects {

    NSMutableArray *dataArray = [[NSArray alloc] init];
    for (NSManagedObject *managedObject in managedObjects) {
        [dataArray addObject:[self dataStructureFromManagedObject:managedObject]];
    }
    return [dataArray autorelease];
}

Itu NSMutableArray *dataArray = [[NSArray alloc] init]; // This should be NSMutableArray

seharusnya begitu NSMutableArray *dataArray = [[NSMutableArray alloc] init];

itu semuanya.

Terima kasih

creativeKoder
sumber
10

Sinkronisasi Data Inti dengan Rails adalah presentasi mendetail yang menyertakan kode sampel untuk membuat serial / deserialisasi objek Data Inti Anda ke / dari JSON (lompat ke slide 55 untuk bagian Data Inti). Kode sampelnya mengasumsikan model yang cukup sederhana tanpa hubungan, meskipun menurut saya akan sangat mudah untuk diperluas.

Presentasi juga membahas beberapa detail tentang menjaga model Data Inti Anda tetap sinkron dengan aplikasi web berbasis REST, dengan petunjuk ke beberapa pustaka yang berguna, termasuk ObjectiveResource dan ASIHTTPRequest . Tidak yakin apakah itu yang Anda coba lakukan, tetapi itu layak untuk dilihat bahkan untuk kode Data Inti.

Christopher Pickslay
sumber
7

Jika Anda memiliki NSDatedi objek yang dikelola, seperti yang disebutkan di atas di salah satu komentar, Anda akan mengalami masalah membuat serial objek yang berisi NSDate. Perbaikan sederhana adalah menambahkan JSONDataRepresentationmetode untuk NSDatemenggunakan kategori objektif-c.

Tambahkan dua file ini ke proyek Anda:

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
joshaidan
sumber
2

Saya menemukan posting ini yang bekerja dengan sangat baik.

http://touchalicious.com/blog/2009/10/25/turn-core-data-models-into-json.html

Karena ini rekursif, hubungan banyak ke banyak akan terus berputar melalui dirinya sendiri. Untuk menghindari hal ini, saya menambahkan kunci "isExportable" ke kamus info pengguna dari hubungan dalam model Data Inti saya. Anda kemudian dapat memeriksa kunci ini dan memilih untuk tidak mengulang hubungan tanpanya.

masukkan deskripsi gambar di sini

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];
            }
        }
    }
}
Brandon Schlenker
sumber
2

Hanya berpikir saya akan memposting pembaruan cepat untuk pertanyaan ini. Saya mengikuti Jawaban oleh Marcus dan Brandon dan menghasilkan ini untuk mengekspor JSON (masih menggunakan 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;
}

Saya tidak bisa mendapatkan impor berfungsi, mungkin itu ada hubungannya dengan fakta bahwa saya menggunakan Magical Record Saya tidak yakin, jadi saya hanya mengulang melalui aliran JSON yang masuk dan membuat objek secara manual ...

Carl Taylor
sumber
1

Marcus S. Zarra telah menginspirasi saya untuk membawa ide rekursif ke versi yang berfungsi. Dalam versi ini Anda tidak perlu mengatur kunci di CoreData dan Anda dapat memotong dan menempelkannya di proyek Anda :-)

// 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
}

Kuncinya di sini adalah meneruskan entitas induk sebagai argumen ke rekursi, sehingga kita dapat memutuskan hubungan mana yang harus kita isi dengan data. Jadi keduanya berfungsi: dataStructureFromManagedObjectdan managedObjectFromStructuredapat menyandikan dan mendekode objek entitas apa pun dari CoreData ke dalam kamus dan kembali menjadi objek.

MPajak
sumber