Search code examples
swiftcore-datansmanagedobjectnsmanagedobjectcontextxcode14

Swift: Insert codable object into Core Data


I'm getting a response from an API and decoding the response like this:

struct MyStuff: Codable {
    let name: String
    let quantity: Int
    let location: String
}

And I have instance an Entity to map MyStuff:

@objc(Stuff)
public class Stuff: NSManagedObject {
}

extension Stuff {
    @nonobjc public class func fetchRequest() -> NSFetchRequest<Stuff> {
        return NSFetchRequest<Stuff>(entityName: "Stuff")
    }
    @NSManaged public var name: String?
    @NSManaged public var quantity: Int64
    @NSManaged public var location: String?
}

My question is, when I have the response of type MyStuff there is a way to loop thru the keys and map the values to core data?

for example:

let myStuff = MyStuff(name: "table", quantity: 1, location: "kitchen")

let myStuff = MyStuff(name: "table", quantity: 1, location: "kitchen")
        for chidren in Mirror(reflecting: myStuff).children {
            print(chidren.label)
            print(chidren.value)
            /*
             insert values to core data
             */
        }

I'll really appreciate your help


Solution

  • A smart solution is to adopt Decodable in Stuff

    Write an extension of CodingUserInfoKey and JSONDecoder

    extension CodingUserInfoKey {
        static let context = CodingUserInfoKey(rawValue: "context")!
    }
    
    extension JSONDecoder {
        convenience init(context: NSManagedObjectContext) {
            self.init()
            self.userInfo[.context] = context
        }
    }
    

    In Stuff adopt Decodable and implement init(from:), it must be implemented in the class, not in the extension

    @objc(Stuff)
    public class Stuff: NSManagedObject, Decodable {
    
        private enum CodingKeys: String, CodingKey { case name, quantity, location }
        
        public required convenience init(from decoder: Decoder) throws {
            guard let context = decoder.userInfo[.context] as? NSManagedObjectContext else { fatalError("Error: context doesn't exist!") }
            let entity = NSEntityDescription.entity(forEntityName: "Stuff", in: context)!
            self.init(entity: entity, insertInto: context)
            let values = try decoder.container(keyedBy: CodingKeys.self)
            name = try values.decodeIfPresent(String.self, forKey: .name)
            quantity = try values.decodeIfPresent(Int64.self, forKey: .quantity) ?? 0
            location = try values.decodeIfPresent(String.self, forKey: .location)
        }
    }
    

    To decode the JSON you have to initialize the decoder with the convenience initializer

    let decoder = JSONDecoder(context: context)
    

    where context is the current NSManagedObjectContext instance.

    Now you can create Stuff instances directly from the JSON.