How to Use Swift 4 Codable in Core Data

How to use swift 4 Codable in Core Data?

You can use the Codable interface with CoreData objects to encode and decode data, however it's not as automatic as when used with plain old swift objects. Here's how you can implement JSON Decoding directly with Core Data objects:

First, you make your object implement Codable. This interface must be defined on the object, and not in an extension. You can also define your Coding Keys in this class.

class MyManagedObject: NSManagedObject, Codable {
@NSManaged var property: String?

enum CodingKeys: String, CodingKey {
case property = "json_key"
}
}

Next, you can define the init method. This must also be defined in the class method because the init method is required by the Decodable protocol.

required convenience init(from decoder: Decoder) throws {
}

However, the proper initializer for use with managed objects is:

NSManagedObject.init(entity: NSEntityDescription, into context: NSManagedObjectContext)

So, the secret here is to use the userInfo dictionary to pass in the proper context object into the initializer. To do this, you'll need to extend the CodingUserInfoKey struct with a new key:

extension CodingUserInfoKey {
static let context = CodingUserInfoKey(rawValue: "context")
}

Now, you can just as the decoder for the context:

required convenience init(from decoder: Decoder) throws {

guard let context = decoder.userInfo[CodingUserInfoKey.context!] as? NSManagedObjectContext else { fatalError() }
guard let entity = NSEntityDescription.entity(forEntityName: "MyManagedObject", in: context) else { fatalError() }

self.init(entity: entity, in: context)

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

Now, when you set up the decoding for Managed Objects, you'll need to pass along the proper context object:

let data = //raw json data in Data object
let context = persistentContainer.newBackgroundContext()
let decoder = JSONDecoder()
decoder.userInfo[.context] = context

_ = try decoder.decode(MyManagedObject.self, from: data) //we'll get the value from another context using a fetch request later...

try context.save() //make sure to save your data once decoding is complete

To encode data, you'll need to do something similar using the encode protocol function.

Implementing Codable and NSManagedObject simultaneously in Swift

In the end, it sounds like there is no "good" solution when migrating from an API dynamic app without caching to a cached app.

I decided to just bite the bullet and try the method in this Question: How to use swift 4 Codable in Core Data?

EDIT:

I couldn't figure out how to make that work so I used the following solution:

import Foundation
import CoreData

/*
SomeItemData vs SomeItem:
The object with 'Data' appended to the name will always be the codable struct. The other will be the NSManagedObject class.
*/

struct OrderData: Codable, CodingKeyed, PropertyLoopable
{
typealias CodingKeys = CodableKeys.OrderData

let writer: String,
userID: String,
orderType: String,
shipping: ShippingAddressData
var items: [OrderedProductData]
let totals: PaymentTotalData,
discount: Float

init(json:[String:Any])
{
writer = json[CodingKeys.writer.rawValue] as! String
userID = json[CodingKeys.userID.rawValue] as! String
orderType = json[CodingKeys.orderType.rawValue] as! String
shipping = json[CodingKeys.shipping.rawValue] as! ShippingAddressData
items = json[CodingKeys.items.rawValue] as! [OrderedProductData]
totals = json[CodingKeys.totals.rawValue] as! PaymentTotalData
discount = json[CodingKeys.discount.rawValue] as! Float
}
}

extension Order: PropertyLoopable //this is the NSManagedObject. PropertyLoopable has a default implementation that uses Mirror to convert all the properties into a dictionary I can iterate through, which I can then pass directly to the JSON constructor above
{
convenience init(from codableObject: OrderData)
{
self.init(context: PersistenceManager.shared.context)

writer = codableObject.writer
userID = codableObject.userID
orderType = codableObject.orderType
shipping = ShippingAddress(from: codableObject.shipping)
items = []
for item in codableObject.items
{
self.addToItems(OrderedProduct(from: item))
}
totals = PaymentTotal(from: codableObject.totals)
discount = codableObject.discount
}
}

How do I properly use Codable, CoreData and NSSet relationships?

Declare photos as a swift native type

@NSManaged var photos: Set<Photo>

In decoder

photos = try values.decode(Set<Photo>.self, forKey: .photos)

Save to CoreData array of strings with Codable

You need an inverse relationship to Movie in Genre. Add this

@NSManaged var movie: Movie?

and establish the connection in the model file.

Then decode an array of strings, map it to Genre instances and assign self to that relationship at the end of the init method

let genreData = try container.decodeIfPresent([String].self, forKey: .genres) ?? []
let genreArray = genreData.map { name in
let genre = Genre(context: manageObjContext)
genre.name = name
genre.movie = self
return genre
}
self.genres = Set(genreArray)

Consider to use a to-many relationship from Genre to Movie as well because otherwise you will have a lot of Genre instances with the same name. And consider also to reduce the optionals in the Core Data classes. It seems that the JSON source provides always all fields. You can get rid of a lot of redundant code.

Swift: Loading Codable Class into CoreData from JSON is generating Objects with all properties to nil

To summarize from the questions comments:

It's important to remember that CoreData still relies heavily on Objective-C. In your example code, the properties on your class, although expressible as CoreData attributes, the implementation is not being handled by CoreData.

You'll need to add the @NSManaged attribute to your properties like this:

@NSManaged var name: String?
@NSManaged var desc: String?
@NSManaged var image: String?
@NSManaged var quantity: Double?

This will expose them to Obj-C as dynamic and allow CoreData to handle the implementation. This would also help to explain your debugging, in that at runtime the print statement would show values, but the saved managed object had nil values.



Related Topics



Leave a reply



Submit