I've read this and this and spent many hours perusing SO, but I can't seem to figure out how to properly use Decode with an empty array.
Original Code
At first, I had no problems with my code.
Here was my original struct:
struct JobHabit: Codable {
var name: String
var description: String
var assigned: String
var order: Int
var category: Category
var active: Bool
var altName: String
var altNameDate: Double
var altAssigned: String
var altCategory: Category
var altOrder: Int
enum Category: String, Codable {
case dailyJobMorning
case dailyJobEvening
case weeklyJob1
case weeklyJob2
case jobBonus
case quickPoints
case jobJar
case dailyHabit
case weeklyHabit
case habitBonus
}
}
And here was my function:
static func observeJobs(completion: @escaping () -> Void) {
FB.ref
.child(FB.jobs)
.observe(.value, with: { (snapshot) in
guard snapshot.exists() else {
completion()
return
}
for item in snapshot.children {
guard let snap = item as? DataSnapshot else { return }
guard let value = snap.value as? [String: Any] else { return }
do {
let jsonData = try JSONSerialization.data(withJSONObject: value, options: [])
let habit = try JSONDecoder().decode(JobHabit.self, from: jsonData)
jobsMasterList.append(habit)
} catch let error {
print(error)
manuallyDecodeJobHabitAndAddToArray(.dailyJobMorning, value)
}
}
completion()
})
}
That all worked great. No problems.
But then...
I added in another parameter to the struct. I added in a points
parameter that could potentially be empty. It's not optional, but it could be empty. (It's the last parameter.)
Like this:
struct JobHabit: Codable {
var name: String
var description: String
var assigned: String
var order: Int
var category: Category
var active: Bool
var altName: String
var altNameDate: Double
var altAssigned: String
var altCategory: Category
var altOrder: Int
var points: [Point] // <=== new parameter that messed everything up
}
And that caused the decode
function to fail with this error:
keyNotFound(CodingKeys(stringValue: "points", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: "points", intValue: nil) ("points").", underlyingError: nil))
The error makes sense. The decoder can't find a value. Well, if it's empty, then Firebase will return nothing. So that's expected behavior. But why can't the decoder account for that?
I read up on decoding optional values and came up with an initializer for the struct, like so:
struct JobHabit: Codable {
var name: String
var description: String
var assigned: String
var order: Int
var category: Category
var active: Bool
var altName: String
var altNameDate: Double
var altAssigned: String
var altCategory: Category
var altOrder: Int
var points: [Point]
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
self.description = try container.decode(String.self, forKey: .description)
self.assigned = try container.decode(String.self, forKey: .assigned)
self.order = try container.decode(Int.self, forKey: .order)
self.category = try container.decode(Category.self, forKey: .category)
self.active = try container.decode(Bool.self, forKey: .active)
self.altName = try container.decode(String.self, forKey: .altName)
self.altNameDate = try container.decode(Double.self, forKey: .altNameDate)
self.altAssigned = try container.decode(String.self, forKey: .altAssigned)
self.altCategory = try container.decode(Category.self, forKey: .altCategory)
self.altOrder = try container.decode(Int.self, forKey: .altOrder)
self.points = try container.decodeIfPresent([Point].self, forKey: .points) ?? []
}
}
That created a new issue. The issue is that I can't create new instances of my struct anywhere else in the app. When I try doing this:
static let job120 = JobHabit(name: " Bills & finances",
description: "record receipts\nupdate accounts\npay bills\nfiling (max 1 hour)",
assigned: "none",
order: 19,
category: .weeklyJob1,
active: false,
altName: " Bills & finances",
altNameDate: 0,
altAssigned: "none",
altCategory: .weeklyJob1,
altOrder: 19,
points: [])
I get the following error message:
Extra argument 'name' in call
Apparently, the initializer is making it so I can't create new instances of the struct? I'm really struggling to figure out how to decode a potentially empty array from Firebase.
What am I doing wrong? Everything worked fine with decoding BEFORE I added in that empty points
array. Once I added in the points
parameter, decode couldn't handle it, and so I had to add in the initializers in the struct. Now my other code doesn't work.
Oh, and here is my JSON tree sample:
{
"-MOESVPtiXtXQh19sWBP" : {
"active" : true,
"altAssigned" : "Dad",
"altCategory" : "dailyJobMorning",
"altName" : " Morning Job Inspections",
"altNameDate" : 0,
"altOrder" : 0,
"assigned" : "Dad",
"category" : "dailyJobMorning",
"description" : "set job timer\nvisually inspect each person's job...",
"name" : " Morning Job Inspections",
"order" : 0
}
}