运行时仅知数据类型的情况下,如何使用 Swift 4 解析 JSON

11

在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。 - Leo Dabus
将您的Person属性更改为givenName和familyName - Leo Dabus
3
如果我传递 Person.self,它当然可以工作,但关键是我正在尝试在运行时动态执行此操作。而且不确定属性的名称与任何事情有关系。 - Dave Rogers
没有说属性的名称会改变任何东西。 - Leo Dabus
截至Swift 5.6,现在这确实可以工作了。 - nteissler
2个回答

14
您的设计确实独特,但不幸的是,我认为您正在遇到 Swift 类型系统的边缘情况。基本上,协议不能符合自身,因此您一般的 Decodable.Type 在这里是不够的(即,您真正需要一个具体类型来满足类型系统要求)。这可能解释了您遇到的错误:
“无法使用类型列表(T.Type, from: Data)调用 decode。预期使用类型列表(Decodable.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)")

打印预期结果:

人物:Person(名字:“Bob”,姓氏:“Jones”)


@LeoDabus,这些属性来自于registry["Person"]!返回的类型。我的DecodableWrapper只是将解码转发到它特定的类型,而不假设任何特定的数据方案。 - Paulo Mattos
不,它只能是“Person”样的对象。他的代码明确假定(即registry["Person"])将解码类似人的对象。他只是不知道(在编译时)哪个类型将持有所述的人物。对于这种动态机制有多少帮助,目前尚无定论。但是很高兴知道Swift 4编码框架可以解决这种奇特的设计...;) - Paulo Mattos
我感觉我们需要更多信息 -- 从好的@DaveRogers那里 -- 才能充分评估这个设计是否适合他的整体架构。 - Paulo Mattos
1
@LeoDabus...黑客工具箱中的又一款工具。在雨天可能会派上用场,谁知道呢?;) - Paulo Mattos
谢谢Paulo,这是朝着正确方向迈出的伟大一步!就像你所说的那样,静态有点不太好看,但现在它能用了;-) - Dave Rogers

10

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
}

我们在哪里可以修改这段代码,使其能够接受单个对象和数组? - Kilo Loco

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接