I have the following JSON
structure:
{
"base": "USD",
"date": "2020-04-24",
"rates": {
"CAD": 1.4049074074,
"EUR": 0.9259259259
}
}
I'd like to parse it to a class and store it in Core Data
:
class CurrencyRate: NSManagedObject, Decodable {
enum CodingKeys: String, CodingKey {
case base
case date
case rates
}
@NSManaged public var base: String
@NSManaged public var date: Date
@NSManaged public var rates: [String:Double]
required convenience init(from decoder: Decoder) throws {
...
}
From what I read in order to store rates
field in Core Data, I have to create another entity like this and create a one-to-many relationship:
class SubCurrencyRate: NSManagedObject, Decodable {
enum CodingKeys: String, CodingKey {
case currency
case rate
}
@NSManaged public var currency: String
@NSManaged public var rate: Double
required convenience init(from decoder: Decoder) throws {
...
}
And the field rates
in the CurrencyRate
class would be: @NSManaged public var rates: [SubCurrencyRate]
The problem is I don't know how to parse the nested json with dynamic keys into these classes. How can it be done?
And how in general I can decode this part of JSON:
"rates": {
"CAD": 1.4049074074,
"EUR": 0.9259259259
}
into many SubCurrencyRate
objects?
It's not necessary that SubCurrencyRate
conforms to Decodable
but you have to declare the reverse relationship (also in the data model!).
class SubCurrencyRate: NSManagedObject {
@NSManaged public var currency: String
@NSManaged public var rate: Double
@NSManaged public var currencyRate: CurrencyRate
To be able to parse JSON directly in Core Data declare two extensions
extension CodingUserInfoKey {
static let context = CodingUserInfoKey(rawValue: "context")!
}
extension JSONDecoder {
convenience init(context: NSManagedObjectContext) {
self.init()
self.userInfo[.context] = context
}
}
In CurrencyRate
you have to declare the to-many relationship as Set<SubCurrencyRate>
, in init(from decoder
decode the rates dictionary and map it to SubCurrencyRate
instances. If the relationships are defined correctly in the model then the property as well as the reverse relationship is populated automatically.
class CurrencyRate: NSManagedObject, Decodable {
enum CodingKeys: String, CodingKey { case base, date, rates }
@NSManaged public var base: String
@NSManaged public var date: Date
@NSManaged public var rates: Set<SubCurrencyRate>
required convenience init(from decoder: Decoder) throws {
guard let context = decoder.userInfo[.context] as? NSManagedObjectContext else { fatalError("Error: No object context!") }
let entity = NSEntityDescription.entity(forEntityName: "CurrencyRate", in: context)!
self.init(entity: entity, insertInto: context)
let values = try decoder.container(keyedBy: CodingKeys.self)
base = try values.decode(String.self, forKey: .base)
date = try values.decode(Date.self, forKey: .date)
let ratesData = try values.decode([String:Double].self, forKey: .rates)
for (key, value) in ratesData {
let subCurrencyRate = SubCurrencyRate(context: context)
subCurrencyRate.currency = key
subCurrencyRate.rate = value
subCurrencyRate.currencyRate = self
}
}
}
To decode the JSON you have to use the JSONDecoder(context:
API in the extension passing the managed object context and to decode date
properly you have to add an appropriate dateDecodingStrategy
.