struct Reminder: Identifiable {
var id: String = UUID().uuidString
var title: String
var dueDate: Date
var notes: String? = nil
var isComplete: Bool = false
}
is instead decoded from JSON array values (rather than constructed like in the linked example). If each JSON value were to be missing an "id", how would id then be initialized? When trying this myself I got an error keyNotFound(CodingKeys(stringValue: "id", intValue: nil), Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "No value associated with key CodingKeys(stringValue: \"id\", intValue: nil) (\"id\").", underlyingError: nil)).
I still don't get why you want complicate things by not providing ids in json (e.g. "1", "2", "3", could do). If that's still a problem here's a possible simple solution:
struct Reminder: Identifiable, Codable {
var id: String { _id! }
private var _id: String?
var title: String
var dueDate: Date
var notes: String? = nil
var isComplete: Bool = false
mutating func prepare() {
_id = _id ?? UUID().uuidString
}
}
func test(_ data: Data) {
var reminders = try! JSONDecoder().decode([Reminder].self, from: data)
for i in 0 ..< reminders.count {
reminders[i].prepare()
}
print(reminders)
model.state.reminders = reminders // safe to use as all ID's are in there
}
If you do actually want to follow that example exactly and default to having id be generated as UUID().uuidString every time (which, keep in mind, will give you a new UUID every single time you decode a Reminder struct — even the same one repeatedly) then this can be achieved as easily as leaving out id from Reminder's CodingKeys:
struct Reminder: Identifiable, Decodable {
var id: String = UUID().uuidString
var title: String
var dueDate: Date
var notes: String? = nil
var isComplete: Bool = false
private enum CodingKeys: String, CodingKey {
case title, dueDate, notes, isComplete
}
}
This will avoid attempting to decode id from a Decoder at all, which will always give it the assigned default value.
If you'd prefer id to be assigned UUID().uuidString only if id is not found in the JSON, you can achieve this by overriding init(from:):
struct Reminder: Identifiable, Codable {
var id: String = UUID().uuidString
var title: String
var dueDate: Date
var notes: String? = nil
var isComplete: Bool = false
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
title = try container.decode(String.self, forKey: .title)
dueDate = try container.decode(Date.self, forKey: .dueDate)
notes = try container.decodeIfPresent(String.self, forKey: .notes)
isComplete = try container.decode(Bool.self, forKey: .isComplete)
}
}
Two things to note:
In this second example, I made ReminderCodable instead of just Decodable so the compiler would synthesize CodingKeys for us (since it's synthesizing encode(to:)). If you want it to just be Decodable and you implement init(from:) manually, you'll need to also define CodingKeys manually
Both in the original example, and here, isComplete will also throw an error if not found in the JSON, since it's not optional — even though it has a default value. If you want to fall back to false if not present, you'll similarly need to change isComplete = ... to use decodeIfPresent and fall back to false
Huh, I wonder how it got wrapped in an extra .playground package. Thanks for reporting it. In the mean time... you can get at the original with 'Show Package Contents' and opening the nested playground.
Right direction but as written it won't work: decoder won't call the init: for starters it doesn't know which init (of possibly few) to call. You want this:
struct Reminder: Identifiable, Codable {
let id: UUID = UUID() // Warning, see below
var title: String
var dueDate: Date
var notes: String? = nil
var isComplete: Bool = false
}
which has an added benefit of not having the boilerplate, yet at the same time with a drawback of this new warning: "Immutable property will not be decoded because it is declared with an initial value which cannot be overwritten." with a fix-it: "Fix: Make the property mutable instead", which fix-it if you use ... would actually break the thing... no idea how to silence it.
On the second thought there's no ambiguity in the call of the form T(), whether the corresponding init is init() or init(a: Int = 0), etc, so in theory decoder could have called it.... (it doesn't).