I'm building an app in Swift that will take in a store's hours from Firebase and want to represent it in my code as something like "monday.close" to get 02:30. What's the best way to do something like this? Pic below is what the data looks like in Firebase.
init(dictionary: [String: Any]) {
//initialize food item here
self.name = dictionary["name"] as? String ?? ""
self.address = dictionary["address"] as? String ?? ""
self.restaurantLogo = UIImage(named: "")
self.menuItems = dictionary["menuItems"] as? [Food] ?? []
self.deliverUberEats = dictionary["uberEatsLink"] as? String ?? ""
self.deliverDoorDash = dictionary["doordashLink"] as? String ?? ""
self.deliverGrubhub = dictionary["grubhubLink"] as? String ?? ""
self.restaurantID = ""
self.priceRange = dictionary["price"] as? Int ?? 0
self.hoursOpen = dictionary["hours"] as? [String : [String: String]]
}
struct RestaurantHours {
var MonOpen: Date?
var MonClose: Date?
}
CodePudding user response:
I see you already have a dictionary to store the Firebase opening hours, called hoursOpen
.
Instead of using a notation like "monday.open", one option you have is to keep that dictionary as your source of data and retrieve the hours using a function, like this:
func restaurantHours(day: String, when: String) -> String {
let safeWhen = hoursOpen[day.lowercased()] ?? ["open": "closed"]
let safeTime = safeWhen[when.lowercased()] ?? "closed"
return safeTime
}
Here's how you use it (but correct Firebase or parse your data to keep all weekdays lowercased!):
Text(restaurantHours(day: "Monday", when: "open")) // 02.30
Text(restaurantHours(day: "MondAy", when: "close")) // 22.00
Text(restaurantHours(day: "Tuesday", when: "open")) // 02.30
Text(restaurantHours(day: "frIDAy", when: "clOSe")) // closed
CodePudding user response:
Generally you can solve this by using Decodable
protocol. I don't know how your Firebase database / node structure, so I will go with some simple example.
First you can make a helper method that is going to convert your JSON to data to a model of specified type.
func parseData<T: Decodable>(type: T.Type, data: Any) -> Any? {
do {
if case Optional<Any>.none = data {
return nil
}
if data is NSNull {
return nil
}
let modelJSON = try JSONSerialization.data(withJSONObject: data, options: .prettyPrinted)
let jsonDecoder = JSONDecoder()
return try jsonDecoder.decode(T.self, from: modelJSON)
} catch let decodingError {
return decodingError
}
}
Then lets say we have some JSON that comes from Firebase and you want to make Model class based on it ( lets use some standard auth server response):
// MARK: - AuthDataModel
struct AuthDataModel: Codable {
let tokenType: String
let accessToken: String
let refreshToken: String
// If needed, this is how You can match key-names with what you get from Firebase/Server response
enum CodingKeys: String, CodingKey {
case tokenType = "token_type"
case accessToken = "access_token"
case refreshToken = "refresh_token"
}
}
So instead of AuthDataModel
here, You will have your Firebase
node structure based model (if You give me JSON that describe your node, I could suggest you how how it should look).
And then, just when you receive snapshot from Firebase, you do something like this:
func getFirebaseData(completion: @escaping (AuthDataModel?)->Void, failure: @escaping (Error?)->Void) {
yourNodeReference?.getData(completion: { error, snapshot in
guard error == nil else {
print(error?.localizedDescription)
failure(error)
return
}
if let value = snapshot.value {
if let data = self.parseData(type: AuthDataModel.self, data: value) as? AuthDataModel {
completion(data)
} else {
failure(nil)
}
}else{
//whatever you like
}
})
}
This way you have created you model object, and you can access its properties later by authModel.property
eg. authModel.accessToken
.
Now when it comes to Firebase, iirc, null
values can't be stored. So, you will have a case that a model that you define will not be decoded correctly by Codable
protocol, cause one of the keys will be missing.
You can handle that with writing custom init in your model class that conforms to Codable
.
Something like this:
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
self.exampleProperty = try values.decode(String.self, forKey: .exampleProperty)
if values.contains(.someFieldThatCanBeMissing) {
let value = try values.decodeIfPresent(TypeOfThatField.self, forKey: .someFieldThatCanBeMissing)
self.someFieldThatCanBeMissing = value
} else {
self.someFieldThatCanBeMissing = nil
}
}
Also I would maybe go with a bit different kind of node structure. Something like this:
{
"workingDays" : [ {
"close" : "22:30",
"name" : "Monday",
"open" : "10:30"
}, {
"close" : "22:00",
"name" : "Thuesday",
"open" : "10:00"
} ]
}
and then I would model my Model class like this:
struct MySpecificNodeModel: Codable {
let workingDays: [WorkingDay]
}
// MARK: - WorkingDay
struct WorkingDay: Codable {
let close, name, open: String
enum CodingKeys: String, CodingKey {
case close, name, open
}
}
So I have put all working days in an array. You can of course go with a way you do, to have name of days as keys.