Gurmukh Singh
Gurmukh Singh

Reputation: 2017

Swift - is a String or Int?

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

Answers (2)

Shadowrun
Shadowrun

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

Darren
Darren

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

Related Questions