JSON và Dữ liệu cốt lõi trên iPhone


93

Tôi có một biểu đồ đối tượng dữ liệu cốt lõi (bao gồm hai thực thể được liên kết bởi mối quan hệ nhiều).

Tôi rất tò mò, với tư cách là một nhà phát triển iPhone tương đối thiếu kinh nghiệm, liệu có ai có thể đề xuất cách tiếp cận và cách triển khai JSON phù hợp cho iPhone, điều này sẽ cho phép tôi:

  1. chuyển đổi các bản ghi dữ liệu cốt lõi thành một chuỗi JSON (trong khi duy trì mối quan hệ giữa các thực thể); và

  2. chuyển đổi chuỗi JSON trở lại thành các đối tượng dữ liệu cốt lõi (một lần nữa duy trì mối quan hệ giữa các thực thể).

Tôi đã tìm kiếm, không thành công, cho một mẫu hướng dẫn / mã về điểm này, vì vậy mọi sự trợ giúp sẽ được chúng tôi biết ơn.


3
Đối với bất kỳ ai đang tìm hiểu điều này cho iOS5, hiện có NSJSONSerialization developer.apple.com/library/mac/#documentation/Foundation/… stackoverflow.com/questions/6726899/nsjsonserialization-in-ios5
nicerobot

Tôi biết rằng câu hỏi này hơi cũ, nhưng tôi đã tạo một thư viện đơn giản có tên là OSReflectionKit , cho phép bạn tuần tự hóa / giải mã hóa các đối tượng đến / từ JSON, sử dụng NSJSONSerialization hoặc NSDictionary. Nó cũng hỗ trợ các đối tượng Dữ liệu lõi.
Alexandre OS

Câu trả lời:


103

Đầu tiên, hãy chọn một thư viện JSON để sử dụng, cá nhân tôi thích TouchJSON nhưng một số thư viện khác cũng khá hay. Phần phức tạp, mặc dù không quá khó, là chuyển đổi các đối tượng được quản lý của bạn thành các cấu trúc phù hợp cho việc chuyển đổi. Tôi đã viết điều này rất nhanh vì vậy nó có thể có một hoặc hai lỗi :)

Các phương pháp bạn gọi là:

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

Và cách thực hiện như sau:

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

Bây giờ điều này là đệ quy nên bạn có thể dễ dàng dịch toàn bộ kho lưu trữ liên tục của mình nếu bạn không cẩn thận. Theo dõi các mối quan hệ của bạn và đảm bảo rằng chúng chỉ đi "xuống" cây đối tượng để bạn chỉ nhận được những đối tượng bạn muốn dịch.


Cảm ơn bạn một lần nữa vì một câu trả lời xuất sắc khác và cuốn sách rất hữu ích của bạn! :)
Urizen

2
Chào Marcus. Tôi vừa thử đoạn mã ở trên (với một số sửa đổi nhỏ để làm cho nó biên dịch và việc thực thi dường như diễn ra vô thời hạn cho đến khi ứng dụng gặp sự cố). Xin lỗi đã làm phiền bạn nhưng tôi rất tò mò nếu bạn có thể chỉ cho tôi hướng đi đúng để giải quyết vấn đề này. Nó dường như xảy ra với đệ quy trong phương thức datastructFromManagedObject ...
Urizen

1
Phụ thuộc vào cấu trúc dữ liệu của bạn. Nếu mô hình của bạn tạo ra một vòng lặp thì nó sẽ chạy mãi mãi. Xem lại mô hình dữ liệu của bạn và đảm bảo rằng nó là một thiết kế dạng cây hoặc đặt các điểm dừng logic trong mã đệ quy để ngăn lặp lại.
Marcus S. Zarra

1
Bạn đã thực sự thử chạy mã này chưa? Có rất nhiều lỗi. dataStructureForManagedObject thậm chí không tồn tại. Tôi nghĩ rằng nó có thể chỉ là lỗi đánh máy nhưng nếu bạn thay đổi nó thành dataStructureFromManagedObject, toàn bộ mọi thứ chỉ lặp lại vô hạn giữa các cặp quan hệ. Tôi có thiếu một số mã bổ sung ở đây không?
Chris Mitchelmore

1
Ví dụ về mã này đã được viết trong trình duyệt hai năm trước. Nó nhằm mục đích truyền cảm hứng không phải là một bản sao và dán. Đối với vòng lặp vô hạn, điều đó có nghĩa là bạn có một vòng lặp trong mô hình của mình và sau đó bạn sẽ cần thêm logic mô hình cụ thể vào ứng dụng của mình để phá vỡ chu kỳ. Có một số cách để làm điều đó không có trong ví dụ này.
Marcus S. Zarra

12

Tôi chỉ muốn chỉ ra một lỗi đánh máy nhỏ khiến mã bị lỗi và hy vọng điều này sẽ giúp bạn tiết kiệm một vài phút.

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

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

Các NSMutableArray *dataArray = [[NSArray alloc] init]; // This should be NSMutableArray

thực sự nên NSMutableArray *dataArray = [[NSMutableArray alloc] init];

đó là tất cả.

cảm ơn bạn


10

Đồng bộ hóa Dữ liệu lõi với Rails là một bản trình bày chi tiết bao gồm mã mẫu để tuần tự hóa / giải mã hóa các đối tượng Dữ liệu lõi của bạn đến / từ JSON (bỏ qua đến slide 55 cho phần Dữ liệu lõi). Mã mẫu của anh ấy giả định một mô hình khá đơn giản không có mối quan hệ, mặc dù tôi nghĩ rằng nó sẽ khá dễ dàng để mở rộng.

Bài trình bày cũng đi sâu vào một số chi tiết về việc giữ cho mô hình Dữ liệu cốt lõi của bạn đồng bộ với ứng dụng web dựa trên REST, với các con trỏ đến một số thư viện hữu ích, bao gồm ObjectiveResourceASIHTTPRequest . Không chắc đó có phải là những gì bạn đang cố gắng làm hay không, nhưng nó đáng để xem ngay cả đối với mã Dữ liệu cốt lõi.


7

Nếu bạn có một NSDatetrong đối tượng được quản lý của mình, như đã đề cập ở trên trong một trong các nhận xét, bạn sẽ gặp sự cố khi tuần tự hóa đối tượng có chứa NSDate. Một cách khắc phục đơn giản là thêm một JSONDataRepresentationphương pháp NSDatesử dụng các danh mục mục tiêu-c.

Thêm hai tệp này vào dự án của bạn:

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


2

Tôi đã xem qua bài đăng này hoạt động rất tốt.

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

Vì điều này là đệ quy, các mối quan hệ nhiều-nhiều sẽ tiếp tục lặp lại qua chính chúng. Để tránh điều này, tôi đã thêm khóa "isExportable" vào từ điển thông tin người dùng về các mối quan hệ trong mô hình Dữ liệu cốt lõi của tôi. Sau đó, bạn có thể kiểm tra khóa này và chọn không lặp lại các mối quan hệ mà không có nó.

nhập mô tả hình ảnh ở đây

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

2

Chỉ nghĩ rằng id đăng một bản cập nhật nhanh chóng cho câu hỏi này. Tôi đã theo dõi Câu trả lời của Marcus và Brandon và nghĩ ra điều này để xuất JSON (nó vẫn sử dụng 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;
}

Tôi không thể làm cho quá trình nhập hoạt động, có lẽ điều đó có liên quan đến thực tế là tôi đang sử dụng Magical Record, tôi không chắc chắn, vì vậy tôi chỉ cần lặp lại luồng JSON đến và tạo các đối tượng theo cách thủ công ...


1

Marcus S. Zarra đã truyền cảm hứng cho tôi để đưa ý tưởng đệ quy vào một phiên bản hoạt động. Trong phiên bản này, bạn không cần đặt khóa trong CoreData và bạn có thể cắt và dán nó vào dự án của mình :-)

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

Chìa khóa ở đây là chuyển thực thể mẹ làm đối số cho đệ quy, vì vậy chúng ta có thể quyết định mối quan hệ nào chúng ta phải điền vào dữ liệu. Vì vậy, cả hai chức năng: dataStructureFromManagedObjectmanagedObjectFromStructurecó thể mã hóa và giải mã bất kỳ đối tượng thực thể nào từ CoreData thành một từ điển và trở lại thành một đối tượng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.