Условный тип в модели JSON-ответа (Swift 5)

Недавно я работал с API запросом, ответом от которого был JSON, в котором одно из значений могло быть либо Bool, либо String.

Ответ от сервера мог быть таким:

{
"title": "Test",
"coverImage": false,
"coverImageFilename": false
}

Или таким:

{
"title": "Test",
"coverImage": "https://website.com/coverimage.jpg",
"coverImageFilename": "coverimage.jpg"
}

В данном случае если у страницы включена Cover Image, даётся ссылка и название файла. Если картинка не установлена - возвращается false.

Проблема заключалась в том, что структура, написанная под этот json-ответ, не позволяла правильно декодить полученный ответ:

struct PageDetails: Codable {
    public let title: String
    public let coverImage: Bool
    public let coverImageFilename: Bool
}

В одном случае coverImage был Bool, в другом - String, что вызывало ошибку.

Решение

struct ValueOrFalse<T:Codable>: Codable {
    let value: T?
    public init(from decoder:Decoder) throws {
        let container = try decoder.singleValueContainer()
        let falseValue = try? container.decode(Bool.self)
        if falseValue == nil {
            value = try container.decode(T.self)
        } else {
            value = nil
        }
    }
}

Использовать её очень просто:

struct PageDetails: Codable {
    public let title: String
    public let coverImage: ValueOrFalse<String>
    public let coverImageFilename: ValueOrFalse<String>
}
let myJson = """
{
    "title": "Test",
    "coverImage": false,
    "coverImageFilename": false
}
"""
do {
    print(try JSONDecoder().decode(PageDetails.self, from: myJson.data(using: .utf8)!))
} catch {
    print(error)
}

April 14, 2020