Reputation: 2017
I have the following User
Model:
struct User: Encodable, Decodable {
var uid: String
var email: String
var username: String
var created_on: String?
}
In some cases created_on
is being saved as a number
and in some cases a string
.
Is there any way to say that:
var created_on: String?
Can be a string
or number
?
At the moment its just a string
.
Reason is when im decoding
a user from firebase, if its saved as a number
, it crashes. This is because in some cases (old way I did it) it was saved as a string
, but now im saving it as a number
.
Upvotes: 0
Views: 149
Reputation: 3857
In your case you don't want a type that is either one thing or another, you want to decode something that could be encoded as one type or another into a single type. For that you just try decoding it one type at a time. If you did really want to express a type that can be one thing or another then you can use an enum that can be either one thing (e.g. String) or another (e.g. Int)
enum Either<A, B> {
case a(A)
case b(B)
}
extension Either: Decodable where A: Decodable, B: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let a = try? container.decode(A.self) {
self = .a(a)
}
else if let b = try? container.decode(B.self) {
self = .b(b)
}
else {
throw ... some kind of error
}
}
}
let data = """
[{"sOrI":"Three"},
{"sOrI":42}]
"""
.data(using: .utf8)!
struct Thing: Decodable {
let sOrI: Either<String, Int>
}
try? JSONDecoder().decode([Thing].self, from: data)
Upvotes: 1
Reputation: 10398
The simple thing to do here, is to override the default Decodable
implementation and check if it is Int
or String
like this:
struct User: Encodable, Decodable {
var uid: String
var email: String
var username: String
var created_on: String?
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.uid = try container.decode(String.self, forKey: .uid)
self.email = try container.decode(String.self, forKey: .email)
self.username = try container.decode(String.self, forKey: .username)
if let createdOnInt = try? container.decode(Int.self, forKey: .created_on) {
self.created_on = String(createdOnInt)
} else {
self.created_on = try container.decode(String.self, forKey: .created_on)
}
}
}
The CodingKeys
are automatically generated using your var names, however you can override that also allowing you to change the created_on
variable into the standard swift camel case type by adding:
enum CodingKeys: String, CodingKeys {
case uid, email, username
case createdOn = "created_on"
}
Upvotes: 1