Приведенные данные не содержат ошибки значения верхнего уровня при попытке декодирования json в NSManagedObject - PullRequest
0 голосов
/ 05 августа 2020

При переносе приложения из 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 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 элемента

  • .0: TwoVerte.ManagedProfileCodable

▿ .1: Context

  • codingPath: 0 elements
  • debugDescription: «Указанные данные не содержат значения верхнего уровня.»
  • lowerError: nil

Я наткнулся на сообщение без ответа от другого разработчика кто описывает эту же проблему здесь: CoreData NSManagedObject & JSON Encodable не работает

Поскольку JSON действителен и достигнута точка останова внутри initFromDecoder, я не могу подумайте обо всем, что могло бы вызвать этот сбой. Возможно, что-то не так в том, как построен класс. Интересно, сталкивался ли еще кто-нибудь с подобной проблемой.

Ответы [ 3 ]

1 голос
/ 05 августа 2020

Думаю, проблема связана с необязательным типом Bool для isOnline. CoreData не поддерживает сохранение необязательных примитивов.

Если вы создаете NSManagedObject и добавляете логическое свойство.

@NSManaged public var isOnline: Bool?

, вы получите ошибку:

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

В большинстве случаев случаях вы должны иметь возможность сделать его необязательным и использовать значение по умолчанию.

Поскольку вы не можете добавить инициализатор декодирования в расширение. В этом случае я предлагаю установить генерацию кода на none.

Создать класс ManagedProfile и подтвердить его на Codable.

1 голос
/ 05 августа 2020

Причина сбоя в том, что вы создали подкласс для своего основного класса сущности данных, но он должен быть родительским классом, который вы используете, поскольку это то, что вы создаете здесь.

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

Итак отбросьте подкласс и переместите свой код в расширение

extension ManagedProfile: Codable {
     //all code from ManagedProfileCodable
}
0 голосов
/ 16 августа 2020

Я наконец решил проблему. Вместо использования «Codegen: Class Definition» я должен был использовать «Codegen: Manual» и добавить подкласс следующим образом:

  1. Откройте файл модели данных
  2. В разделе Entities выберите соответствующий объект
  3. В верхнем меню выберите Editor -> Create NSManagedObject subclass ...
...