Home > Blockchain >  Swift: Insert codable object into Core Data
Swift: Insert codable object into Core Data

Time:11-09

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

CodePudding user response:

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.

CodePudding user response:

You can store entire object as JSONString if you don't support query for each field. If you need query for some field then keep that field in entity object.

struct MyStuff: Codable {
    let name: String
    let quantity: Int
    let location: String
}
 
extension Encodable {
    func toString() -> String? {
        if let config = try? JSONEncoder().encode(self) {
            return String(data: config, encoding: .utf8)
        }
        return .none
    }
}
 
extension Decodable {
    static func map(JSONString: String) -> Self? {
        try? JSONDecoder().decode(Self.self, from: JSONString.data(using: .utf8) ?? .init())
    }
}
 
 
@objc(Stuff)
public class Stuff: NSManagedObject {
}
 
// Entity with single field (no field base query support)
extension Stuff {
    @nonobjc public class func fetchRequest() -> NSFetchRequest<Stuff> {
        return NSFetchRequest<Stuff>(entityName: "Stuff")
    }
    @NSManaged public var myStuffRawJSON: String?
 
    func mapToMyStuff() -> MyStuff? {
        MyStuff.map(JSONString: myStuffRawJSON ?? "")
    }
}

How to use:

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

let entity: Stuff //Create entity
entity.myStuffRawJSON = myStuff.toString()
// save your entity
  • Related