使用 Decodable 解码 JSON 嵌套字典并使用 Core Data 存储它

Posted

技术标签:

【中文标题】使用 Decodable 解码 JSON 嵌套字典并使用 Core Data 存储它【英文标题】:Decoding JSON nested dictionary using Decodable and storing it using Core Data 【发布时间】:2020-04-28 12:09:02 【问题描述】:

我有以下JSON 结构:


  "base": "USD",
  "date": "2020-04-24",
  "rates": 
    "CAD": 1.4049074074,
    "EUR": 0.9259259259
  

我想将其解析为一个类并将其存储在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 
        ...
    

根据我阅读的内容,为了在 Core Data 中存储 rates 字段,我必须创建另一个这样的实体并创建一对多关系:

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 
        ...
    

CurrencyRate 类中的字段 rates 将是:@NSManaged public var rates: [SubCurrencyRate]

问题是我不知道如何将带有动态键的嵌套 json 解析到这些类中。怎么办?

一般来说我如何解码这部分 JSON:

"rates": 
  "CAD": 1.4049074074,
  "EUR": 0.9259259259

进入 许多 SubCurrencyRate 对象?

【问题讨论】:

创建新实体并添加关系时,您不需要为此(费率)的特定属性,因为该关系将充当费率的属性。从建模的角度来看,尽管将数据分解为货币对和汇率可能会更好,然后您只需要一个实体。 【参考方案1】:

SubCurrencyRate 不必符合Decodable,但您必须声明反向关系(也在数据模型中!)。

class SubCurrencyRate: NSManagedObject 

    @NSManaged public var currency: String
    @NSManaged public var rate: Double
    @NSManaged public var currencyRate: CurrencyRate

为了能够在 Core Data 中直接解析 JSON,声明两个扩展

extension CodingUserInfoKey 
    static let context = CodingUserInfoKey(rawValue: "context")!


extension JSONDecoder 
    convenience init(context: NSManagedObjectContext) 
        self.init()
        self.userInfo[.context] = context
    

CurrencyRate 中,您必须将一对多关系声明为Set<SubCurrencyRate>,在init(from decoder 中解码费率字典并将其映射到SubCurrencyRate 实例。如果在模型中正确定义了关系,则属性以及反向关系会自动填充。

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
        
    

要解码 JSON,您必须在传递托管对象上下文的扩展中使用 JSONDecoder(context: API 并正确解码 date,您必须添加适当的 dateDecodingStrategy

【讨论】:

【参考方案2】:

前段时间我写了一封blog post 来讨论这个问题。 长话短说:我建议使用 DTO 解码成 CoreData 模型。鉴于 CoreData 模型总是绑定到一个上下文(在从 JSON 解码时您没有)这一事实 - 拥有表示 JSON 的简单结构然后从这些结构创建您的 CoreData 模型会更容易。

【讨论】:

以上是关于使用 Decodable 解码 JSON 嵌套字典并使用 Core Data 存储它的主要内容,如果未能解决你的问题,请参考以下文章

字符串字典:任何不符合协议“可解码”[重复]

当属性类型可能从 Int 更改为 String 时,如何使用 Decodable 协议解析 JSON?

快速解码 JSON 数组或字典错误

Swift 4 Decodable - 将 JSON 对象解码为“数据”

Swift 4 JSON Decodable 解码类型更改的最简单方法

JSON 可解码嵌套数组具有不同的格式和类型