How to parse JSON with Decodable protocol when property types might change from Int to String? [duplicate]

Well it’s a common IntOrString problem. You could just make your property type an enum that can handle either String or Int.

enum IntOrString: Codable {
    case int(Int)
    case string(String)
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        do {
            self = try .int(container.decode(Int.self))
        } catch DecodingError.typeMismatch {
            do {
                self = try .string(container.decode(String.self))
            } catch DecodingError.typeMismatch {
                throw DecodingError.typeMismatch(IntOrString.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Encoded payload conflicts with expected type, (Int or String)"))
            }
        }
    }
    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        switch self {
        case .int(let int):
            try container.encode(int)
        case .string(let string):
            try container.encode(string)
        }
    }
}

As I have found mismatch of your model that you posted in your question and the one in the API endpoint you pointed to, I’ve created my own model and own JSON that needs to be decoded.

struct PostModel: Decodable {
    let userId: Int
    let id: Int
    let title: String
    let body: String
    let postCode: IntOrString
    // you don't need to implement init(from decoder: Decoder) throws
    // because all the properties are already Decodable
}

Decoding when postCode is Int:

let jsonData = """
{
"userId": 123,
"id": 1,
"title": "Title",
"body": "Body",
"postCode": 9999
}
""".data(using: .utf8)!
do {
    let postModel = try JSONDecoder().decode(PostModel.self, from: jsonData)
    if case .int(let int) = postModel.postCode {
        print(int) // prints 9999
    } else if case .string(let string) = postModel.postCode {
        print(string)
    }
} catch {
    print(error)
}

Decoding when postCode is String:

let jsonData = """
{
"userId": 123,
"id": 1,
"title": "Title",
"body": "Body",
"postCode": "9999"
}
""".data(using: .utf8)!
do {
    let postModel = try JSONDecoder().decode(PostModel.self, from: jsonData)
    if case .int(let int) = postModel.postCode {
        print(int)
    } else if case .string(let string) = postModel.postCode {
        print(string) // prints "9999"
    }
} catch {
    print(error)
}

Leave a Comment