仅在运行时知道类型时的 Swift 4 JSON 解码

Posted

技术标签:

【中文标题】仅在运行时知道类型时的 Swift 4 JSON 解码【英文标题】:Swift 4 JSON decoding when type is only known at runtime 【发布时间】:2018-03-17 16:19:45 【问题描述】:

当要解码的类型在运行时知道时,Swift 4 中的Decodable 协议是否可以解码 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 :-(

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

【问题讨论】:

你应该通过 Person.self 而不是 t 并将您的 Person 属性更改为 givenName 和 familyName 是的,如果我通过 Person.self 它当然可以工作,但重点是我试图在运行时动态地执行此操作。也不确定属性的名称与什么有关? 没有说属性名称会改变什么 【参考方案1】:

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

无法使用(Decodable.Type, from: Data) 类型的参数列表调用decode。需要 (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)")

打印预期结果:

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

【讨论】:

@LeoDabus 属性来自registry["Person"]! 返回的类型。我的DecodableWrapper 仅将解码转发到他的特定类型,而不假设任何特定的数据方案。 不,它只能是Person-ish 对象。他的代码清楚地假设(即registry["Person"])一个类似人的对象将被解码。他只是不知道(在编译时)哪种 类型将容纳所述人。陪审团不知道这种动态机制对他有多大帮助。但很高兴知道 Swift 4 编码框架 con 可以解决这种奇特的设计... ;) 我觉得我们需要更多信息——来自优秀的@DaveRogers——来全面评估这个设计是否适合他的整体架构。 @LeoDabus ...黑客工具箱中的另一个工具。下雨天可能会派上用场,谁知道呢? ;) 感谢 Paulo,这是朝着正确方向迈出的一大步!正如你所说,静电确实感觉有点脏,但现在可以了;-)【参考方案2】:

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

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

【讨论】:

我们在哪里可以修改此代码以使其同时接受单个对象和数组?

以上是关于仅在运行时知道类型时的 Swift 4 JSON 解码的主要内容,如果未能解决你的问题,请参考以下文章

JSON仅在所有单元格Swift 4中显示最后一个可选

当您不知道 Swift 中的项目类型时,如何解码嵌套的 JSON 数据? [复制]

使用 Lambda 表达式调用通用方法(以及仅在运行时知道的类型)

奇怪的 Swift 4 类型系统

在 Swift 2 中设置多个类属性时的防护

swift3.0解疑@objc