Json Batch insertion: Corrected sample from WWDC19 needed

Hi,

I am trying to load JSON into Coredata. Something similar to what was presented WWDC 2019.

There are discrepencies on slide 49 and 51. The Type returned by JSON decoder on 49 is [[String: Any]] and is different than 59 [String: Any]

Moreover, the code does not seem to work?


Did anyone get it to work? Is it possible to provide example?




let rawPostsData = // ...
if let postDicts = try? JSONSerialization.jsonObject(with:rawPostsData) as? [String : Any] {

moc.perform {
let insertRequest = NSBatchInsertRequest(entity: Post.entity(), objects: postDicts) let insertResult = try? moc.execute(insertRequest) as! NSBatchInsertRequest
let success = insertResult.result as! Bool

} }


Replies

Give something like this a try:



func syncLocations() {
        var batchCreate: [[String: Any]] = []
        for location in remoteLocations {
            if let objectID = locationDataSource.performFetchObjectID(Location.requestLocationStruct(location)) {
                let updateObj = locationDataSource.updateContext.object(with: objectID) as! Location
                let (latitude, longitude) = invalidCoordinates(updateObj.provider, updateObj.slug)
                updateObj.latitude = latitude
                updateObj.longitude = longitude
                locationDataSource.saveUpdateObject()
            } else {
                let (latitude, longitude) = invalidCoordinates(location.provider, location.slug!)
                batchCreate.append(
                    [
                        "uniqueID": UUID().uuidString,
                        "title": location.title,
                        "name": location.name ?? "",
                        "slug": location.slug ?? "",
                        "subtitle": location.subtitle ?? "",
                        "locale": location.locale ?? "",
                        "provider": location.provider,
                        "latitude": latitude,
                        "longitude": longitude
                    ])
                }
        }
        let request = NSBatchInsertRequest(entityName: Location.entityName, objects: batchCreate)
        locationDataSource.performBatchInsert(request, "location")
    }

...

    func performBatchInsert(_ request: NSBatchInsertRequest, _ author: String = appTransactionAuthorName) {
        do {
            createContext.transactionAuthor = author
            try createContext.executeAndMergeChanges(using: request)
        } catch {
            print(error)
        }
    }

extension NSManagedObjectContext {
    public func executeAndMergeChanges(using batchInsertRequest: NSBatchInsertRequest) throws {
        batchInsertRequest.resultType = .objectIDs
        let result = try execute(batchInsertRequest) as! NSBatchInsertResult
        let changes: [AnyHashable: Any] = [NSInsertedObjectsKey: result.result as? [NSManagedObjectID] ?? []]
        NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes, into: [self])
    }
}

The batch insert request basically wants an array of objects that are themselves an array of property key-values. As such, you'll need to make sure the JSON decodes to [[String: Any]]

I got it to work.


There are some spelling mistakes on the presentation.

The objects you pass in need to have the type [[String : Any]]

So you basically have have key value pairs in a list. The strings must match the attribute names in your core data model.


Also you need to cast the insertResult to NSBatchInsertResult. Not NSBatchInsertRequest.


I used an example entity CDTest with the attributes "id" of type Int32 and "name" of type string. I made "id" the constraint.

The following code worked for me then:


class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        self.batchInsertExamples()
    }

    func batchInsertExamples() {
        
        var objectsToInsert: [[String : Any]] = []
        for i in 0...5 {
            objectsToInsert.append(["id" : i, "name" : "My Name"])
        }
        
        self.persistentContainer.viewContext.performAndWait {
            do {
                let insertRequest = NSBatchInsertRequest(entity: CDTest.entity(), objects: objectsToInsert)
                let insertResult = try self.persistentContainer.viewContext.execute(insertRequest) as! NSBatchInsertResult
                
                let success = insertResult.result as! Bool
                if !success {
                    print("batch insert was not successful")
                }
            } catch {
                print(error.localizedDescription)
            }
            
            if self.persistentContainer.viewContext.hasChanges {
                do {
                    try self.persistentContainer.viewContext.save()
                } catch {
                    print("Error: \(error)\nCould not save Core Data context.")
                    return
                }
            }
        }
    }

    lazy var persistentContainer: NSPersistentContainer = {
        let container = NSPersistentContainer(name: "nsbatchinsert")
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
            
            container.viewContext.automaticallyMergesChangesFromParent = true
            container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
            container.viewContext.undoManager = nil
            container.viewContext.shouldDeleteInaccessibleFaults = true
            
        })
        return container
    }()

}