iOS-Swift : Serialize a NSManagedObject to JSON (with relationships)

て烟熏妆下的殇ゞ 提交于 2020-01-01 09:55:29

问题


After spending too much time trying to find the best practices, here I am once again asking for some help, hoping I am not the only one struggling with this :

I have NSManaged Objects like these :

import Foundation
import CoreData

class Credential: NSManagedObject {

    @NSManaged var credentialArrivalDate: String?
    @NSManaged var credentialBarCode: String?
    @NSManaged var credentialComment: String?
    @NSManaged var credentialCountCheckIn: Int
    @NSManaged var credentialCountCheckOut: Int
    @NSManaged var credentialDepartureDate: String?
    @NSManaged var credentialId: String?
    @NSManaged var credentialIsArrived: Bool
    @NSManaged var credentialIsCheckedOut: Bool
    @NSManaged var credentialIsHost: Bool
    @NSManaged var credentialLastModificationDate: String?
    @NSManaged var credentialMemberControlled: String?
    @NSManaged var credentialNumberOfPeople: Int
    @NSManaged var credentialRSVP: Int
    @NSManaged var credentialTypeId: String?
    @NSManaged var credentialTypeName: String?
    @NSManaged var credentialZoneId: String?
    @NSManaged var credentialZoneName: String?
    @NSManaged var credentialHosts: Set<Host>?
    @NSManaged var credentialMember: Member
    @NSManaged var credentialExtensionFields: Set<ExtensionFields>?
    @NSManaged var credentialDeltaFirstCheckIn: String?
    @NSManaged var credentialDeltaFirstCheckOut: String?
    @NSManaged var credentialIsCheckedIn: Bool

}

I have a relationship with a Member Entity :

import Foundation
import CoreData

class Member: NSManagedObject {

    @NSManaged var memberCompany: String
    @NSManaged var memberEMail: String
    @NSManaged var memberFirstName: String
    @NSManaged var memberId: String
    @NSManaged var memberJob: String
    @NSManaged var memberLastModificationDate: String
    @NSManaged var memberLastName: String
    @NSManaged var memberPhoneNumber: String
    @NSManaged var memberTitle: String
    @NSManaged var memberCredential: Credential

}

What I am trying to do is to serialize my Credential object to JSON using NSJSONSerialization.dataWithJSONObject to generate my JSON and POST it to a Server.

However, I've got an error while serializing my object, and I believe this is because my object does not respect this (from Apple documentation) :

An object that may be converted to JSON must have the following properties:

  • The top level object is an NSArray or NSDictionary.
  • All objects are instances of NSString, NSNumber, NSArray, NSDictionary, or NSNull.
  • All dictionary keys are instances of NSString.
  • Numbers are not NaN or infinity.

So how can I Serialize my Object to JSON ? Should I find some way to convert a Member to a Dictionary, then try to find a way to set this dictionary as the value for my relationship with a credential ?

Have I missed a built-in mapper that handles it ?

I am sorry if that is not clear but any help would be very nice.

In advance, thank you.

Hugo

EDIT :

As suggested in an answer, I tried to fetch a dictionary instead of a NSManagedObject but the logged dictionary is empty (however, the same code worked for a very simple class with a few string attributes and no relationship), here is the code :

/*******************************
    TEST : Fetch dictionary instead of NSManagedObject

    */

    // create Credential entity
    let newCredential = NSEntityDescription.insertNewObjectForEntityForName("Credential", inManagedObjectContext: self.managedObjectContext!) as! Credential

    // create Credential entity
    let newMember = NSEntityDescription.insertNewObjectForEntityForName("Member", inManagedObjectContext: self.managedObjectContext!) as! Member

    // instanciate some fields
    newCredential.credentialId = "44444"
    newMember.memberFirstName = "TEST"

    // set the relationship
    newCredential.credentialMember = newMember

    // retrieve currentCredential
    let requestGuest = NSFetchRequest(entityName: "Credential")

    // get a dictionary instead of NSManagedObject
    requestGuest.resultType = NSFetchRequestResultType.DictionaryResultType

    let fetchedGuest = (managedObjectContext!.executeFetchRequest(requestGuest, error:nil))! // no need to cast here ?

    println(fetchedGuest.description)

    /*******************************/

回答1:


I tried the same but could not get succeed with one to one and one to many relationships. So I wrote a parser to convert managed objects to dictionary and array. You may use it;

//Tested on xCode 6.4 - Swift 1.2
let requestGuest = NSFetchRequest(entityName: "Credential")
let fetchedGuest = (managedObjectContext!.executeFetchRequest(requestGuest, error:nil))!

let dataInArr:NSArray = ManagedParser.convertToArray(fetchedGuest);
NSLog("dataInArr \(dataInArr)");

//--

//
//  ManagedParser.swift
//  Created by Shoaib on 7/29/15.
//

import UIKit
import CoreData

class ManagedParser: NSObject {


    class func convertToArray(managedObjects:NSArray?, parentNode:String? = nil) -> NSArray {

        var rtnArr:NSMutableArray = NSMutableArray();

        if let managedObjs:[NSManagedObject] = managedObjects as? [NSManagedObject] {
            for managedObject:NSManagedObject in managedObjs {
                rtnArr.addObject(self.convertToDictionary(managedObject, parentNode: parentNode));
            }
        }

        return rtnArr;
    } //F.E.

    class func convertToDictionary(managedObject:NSManagedObject, parentNode:String? = nil) -> NSDictionary {

        var rtnDict:NSMutableDictionary = NSMutableDictionary();

        let entity:NSEntityDescription = managedObject.entity;
        let properties:[String] = (entity.propertiesByName as NSDictionary).allKeys as! [String];

        let parentNode:String = parentNode ?? managedObject.entity.name!;
        for property:String in properties  {
            if (property.caseInsensitiveCompare(parentNode) != NSComparisonResult.OrderedSame)
            {
                let value: AnyObject? = managedObject.valueForKey(property);

                if let set:NSSet = value as? NSSet  {
                    rtnDict[property] = self.convertToArray(set.allObjects, parentNode: parentNode);
                } else if let vManagedObject:NSManagedObject = value as? NSManagedObject {
                    if (vManagedObject.entity.name != parentNode) {
                        rtnDict[property] = self.convertToDictionary(vManagedObject, parentNode: parentNode);
                    }
                } else  if let vData:AnyObject = value {
                    rtnDict[property] = vData;
                }
            }
        }

        return rtnDict;
    } //F.E.


} //CLS END



回答2:


I have revised answer of @Shoaib because on CoreData I had two way relationship which caused this infinite loop. Now this prevents to process same NSManagedObject twice by using an object set.

//
//  ManagedParser.swift
//  Created by Shoaib on 7/29/15.
//  http://stackoverflow.com/questions/31672558/ios-swift-serialize-a-nsmanagedobject-to-json-with-relationships
//  Mod by dkavraal on 7/28/16
//

import CoreData

class ManagedParser: NSObject {
    private var parsedObjs:NSMutableSet = NSMutableSet();

    func convertToArray(managedObjects:NSArray?, parentNode:String? = nil) -> NSArray {
        let rtnArr:NSMutableArray = NSMutableArray();
        //--
        if let managedObjs:[NSManagedObject] = managedObjects as? [NSManagedObject] {
            for managedObject:NSManagedObject in managedObjs {
                if self.parsedObjs.member(managedObject) == nil {
                    self.parsedObjs.addObject(managedObject);
                    rtnArr.addObject(self.convertToDictionary(managedObject, parentNode: parentNode));
                }
            }
        }

        return rtnArr;
    } //F.E.

    func convertToDictionary(managedObject:NSManagedObject, parentNode:String? = nil) -> NSDictionary {
        let rtnDict:NSMutableDictionary = NSMutableDictionary();
        //-
        let entity:NSEntityDescription = managedObject.entity;
        let properties:[String] = (entity.propertiesByName as NSDictionary).allKeys as? [String] ?? [];
        //--
        let parentNode:String = parentNode ?? managedObject.entity.name!;
        for property:String in properties  {
            if (property.caseInsensitiveCompare(parentNode) != NSComparisonResult.OrderedSame)
            {
                let value: AnyObject? = managedObject.valueForKey(property);
                //--
                if let set:NSSet = value as? NSSet {
                    rtnDict[property] = self.convertToArray(set.allObjects, parentNode: parentNode);
                } else if let orderedset:NSOrderedSet = value as? NSOrderedSet {
                    rtnDict[property] = self.convertToArray(NSArray(array: orderedset.array), parentNode: parentNode);
                } else if let vManagedObject:NSManagedObject = value as? NSManagedObject {
                    if self.parsedObjs.member(managedObject) == nil {
                        self.parsedObjs.addObject(managedObject);
                        if (vManagedObject.entity.name != parentNode) {
                            rtnDict[property] = self.convertToDictionary(vManagedObject, parentNode: parentNode);
                        }
                    }
                } else  if let vData:AnyObject = value {
                    rtnDict[property] = vData;
                }
            }
        }

        return rtnDict;
    } //F.E.


} //CLS END



回答3:


You could achieve this using a third-party framework like Groot or Sync.




回答4:


With the passage of time, this answer may be out of date, but have you explored the use of

func URIRepresentation() -> NSURL

from the NSManagedObjectID class? Once you have a URL, you can turn that into a String to put into your JSON Object, which means that it will serialise properly. With the appropriate structure around these keys, then the relationships can be exported.

When you want to reconstitute the ObjectID, you then use the

func managedObjectIDForURIRepresentation(_ url: NSURL) -> NSManagedObjectID?

from your instance of NSPersistentStoreCoordinator.




回答5:


I updated answer of @JSBach to Swift 5.

import CoreData

class ManagedParser {
    private var parsedObjs = NSMutableSet()

    func convertToArray(managedObjects: NSArray?, parentNode: String? = nil) -> NSArray {
        let resultArray = NSMutableArray()

        if let managedObjects = managedObjects as? [NSManagedObject] {
            for managedObject in managedObjects {
                if self.parsedObjs.member(managedObject) == nil {
                    self.parsedObjs.add(managedObject)
                    resultArray.add(self.convertToDictionary(managedObject: managedObject,
                                                             parentNode: parentNode))
                }
            }
        }

        return resultArray
    }

    func convertToDictionary(managedObject: NSManagedObject, parentNode: String? = nil) -> NSDictionary {
        let resultDictonary = NSMutableDictionary()

        let entity = managedObject.entity
        let properties: [String] = (entity.propertiesByName as NSDictionary).allKeys as? [String] ?? []

        let parentNode = parentNode ?? managedObject.entity.name!
        for property in properties {
            if property.caseInsensitiveCompare(parentNode) != .orderedSame {
                let value = managedObject.value(forKey: property)

                if let set = value as? NSSet {
                    resultDictonary[property] = self.convertToArray(managedObjects: set.allObjects as NSArray, parentNode: parentNode)
                } else if let orderedset = value as? NSOrderedSet {
                    resultDictonary[property] = self.convertToArray(managedObjects: NSArray(array: orderedset.array), parentNode: parentNode)
                } else if let vManagedObject = value as? NSManagedObject {
                    if self.parsedObjs.member(managedObject) == nil {
                        self.parsedObjs.add(managedObject)
                        if vManagedObject.entity.name != parentNode {
                            resultDictonary[property] = self.convertToDictionary(managedObject: vManagedObject, parentNode: parentNode)
                        }
                    }
                } else if let vData = value {
                    resultDictonary[property] = vData
                }
            }
        }

        return resultDictonary
    }
}


来源:https://stackoverflow.com/questions/31672558/ios-swift-serialize-a-nsmanagedobject-to-json-with-relationships

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!