如何处理 Swift 中一个键的两种可能类型的解码?

How do I handle decoding two possible types for one key in Swift?

我有一个用于解码传入 JSON 的 Codable 结构。不幸的是,有时它的键值之一是字符串,有时是浮点数。我能够在下面拼凑几个 do/try/catch 块来让它工作,但是有更好的方法来处理这个问题吗?

struct Project: Codable {
    public let version: Float

    init(from decoder: Decoder) throws {
        var decodedVersion: Float = 1.0
        do {
            decodedVersion = try values.decode(Float.self, forKey: .version)
        } catch {
            do {
                if let inVersion = try Float(values.decode(String.self, forKey: .version)) {
                    decodedVersion = inVersion
                }
            } catch {
                throw error
            }
        }
        version = decodedVersion
    }
}

如果在你的 JSON 中与键关联的值有时是 Float 有时是 String (除了在后端修复这个错误)你可以按照这种方法.

假设这是你的 "funny" JSON

let data = """
[
{
    "magicField": "one"
},
{
    "magicField":1
}
]
""".data(using: .utf8)!

很好,我们如何在Swift中优雅地表示这种数据?

struct Element:Decodable {
    let magicField: ???
}

我们希望 magicField 始终有一个值,有时是 Float,有时是 String

我们可以用量子力学解决这个问题……或者用枚举

让我们定义这个类型

enum QuantumValue: Decodable {

    case float(Float), string(String)

    init(from decoder: Decoder) throws {
        if let float = try? decoder.singleValueContainer().decode(Float.self) {
            self = .float(float)
            return
        }

        if let string = try? decoder.singleValueContainer().decode(String.self) {
            self = .string(string)
            return
        }

        throw QuantumError.missingValue
    }

    enum QuantumError:Error {
        case missingValue
    }
}

如您所见,QuantumValue 类型的值可以包含 FloatString。始终为 1 个且恰好为 1 个值。

元素

我们现在可以定义 JSON

的一般元素
struct Element:Decodable {
    let magicField: QuantumValue
}

解码

就是这样。让我们最终解码 JSON.

if let elms = try? JSONDecoder().decode([Element].self, from: data) {
    print(elms)
}

结果

[
Element(magicField: QuantumValue.string("one")),
Element(magicField: QuantumValue.float(1.0))
]

更新(回答 Rob 的评论)

switch magicField {
    case .string(let text):
        println(text)
    case .float(let num):
        println(num)
}