Reputation: 3112
If have the following code, which should transform a [String: any]
document from Firestore into a struct.
When I debug at that time all requirements are met but after returning the value is nil
.
I tried changing the init?
to a regular init
and an else { fatalError() }
on the guard
. This works and returns a valid struct if data is valid.
What am I doing wrong with the failable initializer?
This does not work (always returns nil, even with valid data):
struct Banner {
let destinationUrl: URL
let imageUrl: URL
let endTime: Date
let startTime: Date
let priority: Int
let trackingKeyClicked: String
let trackingKeyDismissed: String
init?(document: [String: Any]) {
guard
let destinationUrlString = document["destinationUrl"] as? String,
let destinationUrl = URL(string: destinationUrlString),
let imageUrlString = document["imageUrl"] as? String,
let imageUrl = URL(string: imageUrlString),
let priority = document["priority"] as? Int,
let trackingKeyClicked = document["trackingKeyClicked"] as? String,
let trackingKeyDismissed = document["trackingKeyDismissed"] as? String,
let startTime = document["startTime"] as? Date,
let endTime = document["endTime"] as? Date
else { return nil }
self.destinationUrl = destinationUrl
self.imageUrl = imageUrl
self.priority = priority
self.trackingKeyClicked = trackingKeyClicked
self.trackingKeyDismissed = trackingKeyDismissed
self.endTime = endTime
self.startTime = startTime
}
}
// using it like this
let bannerStructs = querySnapshot.documents.map { Banner(document: $0.data()) }
This works with valid data (but crashes on wrong data instead of returning nil):
struct Banner {
let destinationUrl: URL
// ...
let endTime: Date
init(document: [String: Any]) {
guard
let destinationUrlString = document["destinationUrl"] as? String,
let destinationUrl = URL(string: destinationUrlString),
// ....
let endTime = document["endTime"] as? Date
else { fatalError() }
self.destinationUrl = destinationUrl
// ...
self.endTime = endTime
}
}
Upvotes: 1
Views: 201
Reputation: 6547
If none of your guard let
conditions are met it will fail and eventually trigger a fatalError or return nil, depending on which implementation you use.
Please debug well which of the data is not parsed/casted correctly.
I setup a good example on how you might expect it to work and a bad example to let you know how one attribute
that is not expected in that format can make the initialiser
return nil
:
import Foundation
struct Banner {
let destinationUrl: URL
let imageUrl: URL
let endTime: Date
let startTime: Date
let priority: Int
let trackingKeyClicked: String
let trackingKeyDismissed: String
init?(document: [String: Any]) {
guard
let destinationUrlString = document["destinationUrl"] as? String,
let destinationUrl = URL(string: destinationUrlString),
let imageUrlString = document["imageUrl"] as? String,
let imageUrl = URL(string: imageUrlString),
let priority = document["priority"] as? Int,
let trackingKeyClicked = document["trackingKeyClicked"] as? String,
let trackingKeyDismissed = document["trackingKeyDismissed"] as? String,
let startTime = document["startTime"] as? Date,
let endTime = document["endTime"] as? Date
else { return nil }
self.destinationUrl = destinationUrl
self.imageUrl = imageUrl
self.priority = priority
self.trackingKeyClicked = trackingKeyClicked
self.trackingKeyDismissed = trackingKeyDismissed
self.endTime = endTime
self.startTime = startTime
}
}
// using it like this
let goodData:[String:Any] = [
"destinationUrl": "http://destination--url",
"imageUrl": "http://image-url",
"priority": 17,
"trackingKeyClicked": "Tracking Key Clicked",
"trackingKeyDismissed": "Tracking Key Dismissed",
"startTime": Date(),
"endTime": Date()
]
let goodBannerStructs = Banner(document: goodData)
let badData:[String:Any] = [
"destinationUrl": "http://destination--url",
"imageUrl": "http://image-url",
"priority": 17,
"trackingKeyClicked": "Tracking Key Clicked",
"trackingKeyDismissed": "Tracking Key Dismissed",
"startTime": "17 December",
"endTime": Date()
]
let badBannerStructs = Banner(document: badData)
print("Good banner: \(goodBannerStructs)")
print("Bad banner: \(badBannerStructs)")
This is what it prints out:
Good banner: Optional(Banner(destinationUrl: http://destination--url, imageUrl: http://image-url, endTime: 2020-01-21 17:45:27 +0000, startTime: 2020-01-21 17:45:27 +0000, priority: 17, trackingKeyClicked: "Tracking Key Clicked", trackingKeyDismissed: "Tracking Key Dismissed"))
Bad banner: nil
You can try this code on: http://online.swiftplayground.run/
It can be one the dictionary keys of document
might be incorrect with what is coming from the query, might be that priority
might not be an Int or the dates might be String. You have to debug it.
Upvotes: 1
Reputation: 1540
This does not work (always returns nil, even with valid data)
Since your guard
is always failing, the data seems to be incorrect. I guess that startDate
and endDate
aren't that easy to be converted to Date
. Could you please post a example of the json data?
If this is the cause, here is someone describing how to use a DateFormatter
to create a date from a string. If your dates follow ISO8601 you can use Apples ISO8601DateFormatter
to do it.
Upvotes: 0
Reputation: 13
If the failable initialiser is returning nil and the normal initialiser is crashing because of bad data then that points me towards the guard statement in the failable initialiser failing leading to it returning nil. Place a breakpoint on the return nil line within the guard statement and see if this is being hit.
Upvotes: 1