问题
While migrating an app from Realm to CoreData, I'm attempting to parse the following JSON structure:
{
"username": "972542052677",
"displayName": "Arik",
"countryCode": "972",
"status": "Busy",
"walletPublicKey": "XgWDiGMFRPDRVnRq8nxu7NyMLuT4Uez7mJ",
"lastSeen": "2020-08-05T08:12:57.5267881",
"isOnline": false
}
I need to create an NSManagedObject from this data so I'm following this tutorial: https://medium.com/@andrea.prearo/working-with-codable-and-core-data-83983e77198e
This is the model. It inherits from the automatically generated "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)
}}
This is the decoder:
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
}()
This is the part that does the actual decoding. T stands for ManagedProfileCodable, and the existingData variable holds a valid json (I encoded it successfully using the older JSONSerialization library to make sure it is valid)
do
{
let result = try jsonDecoder.decode(T.self, from: existingData)
DispatchQueue.main.async{
completionHandler(NetworkResult<T>.success(result))}
}
catch let error {
...
}
This is the error I receive:
▿ valueNotFound : 2 elements
- .0 : TwoVerte.ManagedProfileCodable
▿ .1 : Context
- codingPath : 0 elements
- debugDescription : "The given data did not contain a top-level value."
- underlyingError : nil
I came across an unanswered post from another developer who describes this same issue over here: CoreData NSManagedObject & JSON Encodable not working
Since the JSON is valid and the break point inside the initFromDecoder is reached, I can't think of anything that might cause this failure. Probably something is wrong in the way the class is built. I wonder if anyone else has also encountered a similar issue.
回答1:
I think issue is optional Bool type for isOnline. CoreData does not support saving optional primitives.
If you create NSManagedObject and add boolean property.
@NSManaged public var isOnline: Bool?
You will get error:
property cannot be marked @NSManaged because its type cannot be represented in Objective-C
In most of the cases you should be able to make it non-optional and use default.
Since you can't add decode initialiser in extension. In this case, I suggest to set code generation to none
.
Create ManagedProfile class and confirm to Codable.
回答2:
The reason it fails is because you have created a sub-class to your core data entity class but it needs to be the parent class you use since that is what you are instantiating here
self.init(entity: entity, insertInto: managedObjectContext)
So drop the sub-class and move your code to an extension
extension ManagedProfile: Codable {
//all code from ManagedProfileCodable
}
回答3:
I have finally resolved the issue. Instead using "Codegen: Class Definition", I should have used "Codegen: Manual" and add the subclass like this:
- Open the data model file
- Under Entities, select the relevant entity
- From the upper menu, choose Editor -> Create NSManagedObject subclass...
来源:https://stackoverflow.com/questions/63262517/the-given-data-did-not-contain-a-top-level-value-error-when-attempting-to-decode