I'm having trouble decoding an array of subclassed super classes and getting the correct full subclass instance. When I run tests, the JSON is written correctly but I get a fatal error when trying to read and decode. It says "Thread 1: Fatal error: The data couldn’t be read because it is missing." I'm pretty sure that my nested decoding is incorrect. (At the advice of ChatGPT, I added the "type" attribute to help with know which subclass to actually init.). Currently I have only one subclass of CheckTask but there will be another once I know how to make this work. Here's what I'm attempting so far...
import Foundation
import Combine
class CheckList: Identifiable, ObservableObject, Codable {
let id: String
var title: String
@Published var tasks: [CheckTask]
var domain: String
....
enum CodingKeys: String, CodingKey {
case id, title, tasks, domain, type
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(String.self, forKey: .id)
title = try container.decode(String.self, forKey: .title)
domain = try container.decode(String.self, forKey: .domain)
var tasksContainer = try container.nestedUnkeyedContainer(forKey: .tasks)
var decodedTasks: [CheckTask] = []
while !tasksContainer.isAtEnd {
let taskContainer = try tasksContainer.nestedContainer(keyedBy: CheckTask.CodingKeys.self)
let type = try taskContainer.decode(String.self, forKey: .type)
switch type {
case "CheckItem":
let item = try tasksContainer.decode(CheckItem.self)
decodedTasks.append(item as CheckTask)
// Add cases for other subclasses as needed
default:
// Handle unknown type or log a warning
print("Unknown task type: \(type)")
}
}
tasks = decodedTasks
}
....
}
import Foundation
class CheckTask: Identifiable, ObservableObject, Equatable, Codable {
var id: UUID
var locked: Bool
....
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(UUID.self, forKey: .id)
locked = try container.decode(Bool.self, forKey: .locked)
}
....
}
import Foundation
class CheckItem: CheckTask {
var title: String
@Published var checkmark: CheckMark
var skippable: Bool = false
var instructions: String
var instructionLinks: [InstructionLink]
....
// CodingKeys to specify custom mapping between Swift names and JSON keys
enum CodingKeys: String, CodingKey {
case type, title, checkmark, skippable, complete, instructions, instructionLinks
}
// Custom decoding method to decode the properties
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
title = try container.decode(String.self, forKey: .title)
checkmark = try container.decode(CheckMark.self, forKey: .checkmark)
skippable = try container.decode(Bool.self, forKey: .skippable)
instructions = try container.decode(String.self, forKey: .instructions)
instructionLinks = try container.decode([InstructionLink].self, forKey: .instructionLinks)
try super.init(from: decoder)
}
....
}
I'm sure that the CheckTask and CheckItem decoding works but it's the CheckList that seems to be the issue. How can I do this correctly? Thanks!