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

5

7

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)

    /*******************************/
Lindgren answered 28/7, 2015 at 9:36 Comment(5)
Have you looked at 3rd party frameworks, like RestKit?Riordan
use fetchRequest.resultType = NSFetchRequestResultType.DictionaryResultType to get data in dictionary from coredata and then serialize it to in json.Pedrick
Isn't it too much for a JSON mapper ? + I'd prefer using swift as much as possibleLindgren
@Pedrick : Thank you for your answer. To make sure I understood it : If I want to create from scratch a Credential object : I create a Credential and a member entity and insert those into my managed object context, set their members, then first fetch the member as a dictionary ? What is the next step ? How am I supposed to "associate" the dictionary representation of my Member to my credential ?Lindgren
medium.com/@vanita.ladkat/…Roughen
P
5

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
Pedrick answered 29/7, 2015 at 10:15 Comment(4)
Thank you, I tried your parser but I've got an EXC_BAD_ACCESS error code = 2 on this line : let properties:[String] = (entity.propertiesByName as NSDictionary).allKeys as! [String];Lindgren
Thanks, it worked better, however the output array is incomplete, is it because I didn't provide every value ? Here's the result : dataInArr ( { credentialCountCheckIn = 0; credentialCountCheckOut = 0; credentialExtensionFields = ( ); credentialHosts = ( ); credentialId = 1; credentialNumberOfPeople = 0; credentialRSVP = 0; } )Lindgren
Yes. Null values would not be part of dictionary.Pedrick
Thanks ! Actually I do need all my keys, event if values are not specified. So what you are suggesting is I should make sure all my fields (let's say it's all textfields) are filled, retrieve their value, and finally turn my managed object to a dictionary if I'm right ?Lindgren
O
2

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

Otti answered 7/8, 2015 at 14:18 Comment(0)
G
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.
//  https://mcmap.net/q/1465098/-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
Glidebomb answered 28/7, 2016 at 16:33 Comment(0)
S
1

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
    }
}
Southernly answered 31/12, 2019 at 23:33 Comment(1)
It throws exception for Date types.Larson
S
0

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.

Sicklebill answered 23/1, 2016 at 10:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.