CoreData NSManagedObject & JSON Encodable not working

Viewed 340

I am trying to create an object class which is both a class conforming Encodable and NSManagedObject, on iOS 13.1.2 and Swift 4.2. This is my class:

import CoreData

class Test: NSManagedObject, Decodable {
    @NSManaged var name: String?

    enum CodingKeys: String, CodingKey {
        case name
    }

    required convenience init(from decoder: Decoder) throws {
        let managedContext = AppDelegate.persistentContainer.viewContext

        guard let entity = NSEntityDescription.entity(forEntityName: "TestEntity", in: managedContext) else { fatalError() }

        self.init(entity: entity, insertInto: managedContext)

        let values = try decoder.container(keyedBy: CodingKeys.self)
        name = try values.decodeIfPresent(String.self, forKey: .name)
    }
}

My data model: Data model

And my persistentContainer in AppDelegate:

static var persistentContainer: NSPersistentContainer = {
    let container = NSPersistentContainer(name: "testContainer")
    container.loadPersistentStores(completionHandler: { (storeDescription, error) in
        if let error = error as NSError? {
            fatalError("Unresolved error \(error), \(error.userInfo)")
        }
    })
    return container
}()

I use this method to create a new instance of my Test class:

do {
    let test = try JSONDecoder().decode(Test.self, from: "{\"name\":\"Test\"}".data(using: .utf8)!)
    print(test.name)
} catch let error {
    print(error)
}

But this gives me the following error:

valueNotFound(Test.Test, Swift.DecodingError.Context(codingPath: [], debugDescription: "The given data did not contain a top-level value.", underlyingError: nil))

What am I doing wrong here?

2 Answers

I finally got this to work. This happened to me because I was using a separate model that I made to conform to NSManagedObject and Decodable, much like the OP's Test class. The answer here got me part of the way there, but what I was missing is that instead of using a separate model, I had to modify the generated entity class to conform to Decodable and use that as my codable model.

So after changing the Codegen to Manual and creating the NSManagedObject class like the link above mentions, your class in the file Test+CoreDataClass.swift becomes this:

import CoreData

@objc(Test)
public class Test: NSManagedObject, Decodable {
    @NSManaged var name: String?

    enum CodingKeys: String, CodingKey {
        case name
    }

    required convenience init(from decoder: Decoder) throws {
        let managedContext = AppDelegate.persistentContainer.viewContext

        guard let entity = NSEntityDescription.entity(forEntityName: "TestEntity", in: managedContext) else { fatalError() }

        self.init(entity: entity, insertInto: managedContext)

        let values = try decoder.container(keyedBy: CodingKeys.self)
        name = try values.decodeIfPresent(String.self, forKey: .name)
    }
}

This can happen when you add your subclass manually by using "New File...".

To add the subclass as an editable class file in your project, instead of adding it by yourself, do the following:

  1. Open the data model file
  2. Under Entities, select the relevant entity
  3. In the Data model Inspector, make sure "Codegen" is set to "Manual"
  4. From the upper menu, choose Editor -> Create NSManagedObject subclass...
Related