当类型仅在运行时已知时,Swift 4 JSON解码

Dav*_*ers 8 json swift swift-protocols swift4 decodable

Decodable当要解码的类型在运行时知道时,Swift 4中的协议是否可以解码JSON对象?

我有一个String类别的注册表,它将标识符映射到我们要解码的类型,如下所示:

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 :-(
Run Code Online (Sandbox Code Playgroud)

我在这里是正确的路线还是有更好的方法?

Pau*_*tos 11

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

无法decode使用类型的参数列表调用(Decodable.Type, from: Data).期望一个类型的参数列表(T.Type, from: Data).

但是,话虽如此,确实有一个(脏!)黑客围绕这个.首先,创建一个虚拟DecodableWrapper来保存您的runtime-ish 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)
    }
}
Run Code Online (Sandbox Code Playgroud)

然后像这样使用它:

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

打印预期结果:

人:姓("Bob",姓:"琼斯")


Mic*_*all 8

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
}
Run Code Online (Sandbox Code Playgroud)