尝试将 json 解码为 NSManagedObject 时,给定数据不包含顶级值错误

The given data did not contain a top-level value error when attempting to decode json into NSManagedObject

将应用程序从 Realm 迁移到 CoreData 时,我正在尝试解析以下 JSON 结构:

{
    "username": "972542052677",
    "displayName": "Arik",
    "countryCode": "972",
    "status": "Busy",
    "walletPublicKey": "XgWDiGMFRPDRVnRq8nxu7NyMLuT4Uez7mJ",
    "lastSeen": "2020-08-05T08:12:57.5267881",
    "isOnline": false
}

我需要根据这些数据创建一个 NSManagedObject,所以我正在学习本教程: https://medium.com/@andrea.prearo/working-with-codable-and-core-data-83983e77198e

这是模型。它继承自自动生成的“ManagedProfile”class:

class ManagedProfileCodable: ManagedProfile, Codable {

enum CodingKeys: String, CodingKey {
    case username           = "username"
    case displayName        = "displayName"
    case email              = "email"
    case status             = "status"
    case walletPublicKey    = "walletPublicKey"
    case countryCode        = "countryCode"
    case isOnline           = "isOnline"
    case lastSeen           = "lastSeen"
}

// MARK: - Decodable
required convenience init(from decoder: Decoder) throws {
    guard let codingUserInfoKeyManagedObjectContext = CodingUserInfoKey.managedObjectContext else{
        fatalError("Failed to get codingUserInfoKeyManagedObjectContext")
    }
    
    guard let managedObjectContext = decoder.userInfo[codingUserInfoKeyManagedObjectContext] as? NSManagedObjectContext else{
        fatalError("Failed to get context from decoder")
    }
    guard let entity = NSEntityDescription.entity(forEntityName: "ManagedProfile", in: managedObjectContext) else {
        fatalError("Failed to read entity desc")
    }
    
    self.init(entity: entity, insertInto: managedObjectContext)

    let container =     try decoder.container(keyedBy: CodingKeys.self)

    self.username =     try container.decodeIfPresent(String.self, forKey: .username)
    self.displayName =  try container.decodeIfPresent(String.self, forKey: .displayName)
    self.email =        try container.decodeIfPresent(String.self, forKey: .email)
    self.status =       try container.decodeIfPresent(String.self, forKey: .status)
    self.walletPublicKey =  try container.decodeIfPresent(String.self, forKey: .walletPublicKey)
    self.isOnline =     try container.decodeIfPresent(Bool.self, forKey: .isOnline) ?? false
    self.countryCode = try container.decodeIfPresent(String.self, forKey: .countryCode)
    
}
// MARK: - Encodable
public func encode(to encoder: Encoder) throws {
    
    var container = encoder.container(keyedBy: CodingKeys.self)
    try container.encode(username, forKey: .username)
    try container.encode(username, forKey: .displayName)
    try container.encode(username, forKey: .email)
    try container.encode(username, forKey: .countryCode)
    try container.encode(username, forKey: .lastSeen)
    try container.encode(username, forKey: .walletPublicKey)
    try container.encode(username, forKey: .status)
    try container.encode(username, forKey: .isOnline)
}}

这是解码器:

var jsonDecoder : JSONDecoder = {
        let decoder = JSONDecoder()
        if let appDelegate = UIApplication.shared.delegate as? AppDelegate{
            if let codingUserInfoKeyManagedObjectContext = CodingUserInfoKey.managedObjectContext{
                decoder.userInfo[codingUserInfoKeyManagedObjectContext] = appDelegate.persistentContainer.viewContext
            }
        }
        return decoder
    }()

这是进行实际解码的部分。 T 代表 ManagedProfileCodable,existingData 变量包含一个有效的 json(我使用较旧的 JSONSerialization 库成功对其进行了编码以确保其有效)

        do
        {
            let result = try jsonDecoder.decode(T.self, from: existingData)
            DispatchQueue.main.async{
                completionHandler(NetworkResult<T>.success(result))}
        }
        catch let error {
...
        }

这是我收到的错误:

▿ valueNotFound : 2 个元素

▿ .1 : 上下文

我发现另一位开发者未回答 post,他在此处描述了同样的问题:CoreData NSManagedObject & JSON Encodable not working

由于 JSON 有效并且达到了 initFromDecoder 内部的断点,我想不出任何可能导致此失败的原因。 class 的构建方式可能有问题。不知道有没有人也遇到过类似的问题。

它失败的原因是因为您已经为您的核心数据实体 class 创建了一个 sub-class 但它必须是您使用的父 class 因为这就是您的身份在这里实例化

self.init(entity: entity, insertInto: managedObjectContext)

所以删除 sub-class 并将您的代码移至扩展程序

extension ManagedProfile: Codable {
     //all code from ManagedProfileCodable
}

我认为问题是 isOnline 的可选 Bool 类型。 CoreData 不支持保存可选基元。

如果创建 NSManagedObject 并添加布尔值 属性.

@NSManaged public var isOnline: Bool?

你会得到错误:

property cannot be marked @NSManaged because its type cannot be represented in Objective-C

在大多数情况下,您应该能够做到 non-optional 并使用默认值。

因为您不能在扩展中添加解码初始化程序。在这种情况下,我建议将代码生成设置为 none.

创建 ManagedProfile class 并确认 Codable。

我终于解决了这个问题。而不是使用“Codegen:Class Definition”,我应该使用“Codegen:Manual”并像这样添加子类:

  1. 打开数据模型文件
  2. 在实体下,select 相关实体
  3. 从上方菜单中,选择编辑器 -> 创建 NSManagedObject 子类...