9

Is it possible with the Decodable protocol in Swift 4 to decode a JSON object when the type to decode to is only known at runtime?

I have a registry of sorts which maps a String identifier to the type we want to decode to, as below:

import Foundation

struct Person: Decodable {
    let forename: String
    let surname: String
}

struct Company: Decodable {
    let officeCount: Int
    let people: [Person]
}

let registry: [String:Decodable.Type] = [
    "Person": Person.self,
    "Company": Company.self
]

let exampleJSON = """
{
    "forename": "Bob",
    "surname": "Jones"
}
""".data(using: .utf8)!

let t = registry["Person"]!

try! JSONDecoder().decode(t, from: exampleJSON) // doesn't work :-(

Am I on the right lines here or is there a better way?

4

2 回答 2

14

您的设计确实是独一无二的,但不幸的是,我相信您遇到了 Swift 类型系统的边缘情况。基本上,一个协议不符合自己,因此,你的一般Decodable.Type在这里是不够的(即,你真的需要一个具体的类型来满足类型系统的要求)。这可能解释了您遇到的错误:

无法decode使用类型为 的参数列表调用(Decodable.Type, from: Data)。需要类型为 的参数列表(T.Type, from: Data)

但是,话虽如此,这确实有一个(肮脏的!)黑客攻击。首先,创建一个虚拟对象DecodableWrapper来保存您的运行时Decodable类型:

struct DecodableWrapper: Decodable {
    static var baseType: Decodable.Type!
    var base: Decodable

    init(from decoder: Decoder) throws {
        self.base = try DecodableWrapper.baseType.init(from: decoder)
    }
}

然后像这样使用它:

DecodableWrapper.baseType = registry["Person"]!
let person = try! JSONDecoder().decode(DecodableWrapper.self, from: exampleJSON).base
print("person: \(person)")

打印预期结果:

人:人(名:“鲍勃”,姓:“琼斯”)

于 2017-10-06T01:19:06.490 回答
11

Paulo的解决方法的缺点是它不是线程安全的。这是一个更简单的解决方案示例,它允许您在没有可用具体类型的情况下解码值:

struct DecodingHelper: Decodable {
    private let decoder: Decoder

    init(from decoder: Decoder) throws {
        self.decoder = decoder
    }

    func decode(to type: Decodable.Type) throws -> Decodable {
        let decodable = try type.init(from: decoder)
        return decodable
    }
}

func decodeFrom(_ data: Data, to type: Decodable.Type) throws -> Decodable {
    let decodingHelper = try JSONDecoder().decode(DecodingHelper.self, from: data)
    let decodable = try decodingHelper.decode(to: type)
    return decodable
}
于 2018-01-09T17:22:51.180 回答