我有一个带有一组值的
JSON:
- [
- { "tag": "Foo",… },{ "tag": "Bar",{ "tag": "Baz",]
我想将此数组解码为一个结构数组,其中特定类型取决于标记:
- protocol SomeCommonType {}
- struct Foo: Decodable,SomeCommonType { … }
- struct Bar: Decodable,SomeCommonType { … }
- struct Baz: Decodable,SomeCommonType { … }
- let values = try JSONDecoder().decode([SomeCommonType].self,from: …)
我怎么做?目前我有这个有点丑陋的包装:
- struct DecodingWrapper: Decodable {
- let value: SomeCommonType
- public init(from decoder: Decoder) throws {
- let c = try decoder.singleValueContainer()
- if let decoded = try? c.decode(Foo.self) {
- value = decoded
- } else if let decoded = try? c.decode(Bar.self) {
- value = decoded
- } else if let decoded = try? c.decode(Baz.self) {
- value = decoded
- } else {
- throw …
- }
- }
- }
然后:
- let wrapped = try JSONDecoder().decode([DecodingWrapper].self,from: …)
- let values = wrapped.map { $0.value }
有没有更好的办法?
解决方法
你的数组包含有限,可枚举变种的异构对象;听起来像Swift枚举的完美用例.它不适用于多态性,因为从概念上讲,这些“事物”不一定是同一种.他们恰好被标记了.
以这种方式看待它:你有一系列都有标签的东西,有些属于这种类型,有些属于完全不同的类型,还有其他……有时你甚至都不认识标签. Swift enum是捕捉这一想法的完美载体.
- struct Foo: Decodable {
- let tag: String
- let fooValue: Int
- }
- struct Bar: Decodable {
- let tag: String
- let barValue: Int
- }
- struct Baz: Decodable {
- let tag: String
- let bazValue: Int
- }
并且您的数组可以包含上述类型的任何实例,或者包含未知类型的实例.所以你有enum TagggedThing(或更好的名字).
- enum TagggedThing {
- case foo(Foo)
- case bar(Bar)
- case baz(Baz)
- case unknown
- }
你的数组,用Swift术语,是[TagggedThing]类型.所以你将TagggedThing类型与Decodable一致,如下所示:
- extension TagggedThing: Decodable {
- private enum CodingKeys: String,CodingKey {
- case tag
- }
- init(from decoder: Decoder) throws {
- let container = try decoder.container(keyedBy: CodingKeys.self)
- let tag = try container.decode(String.self,forKey: .tag)
- let singleValueContainer = try decoder.singleValueContainer()
- switch tag {
- case "foo":
- // if it's not a Foo,throw and blame the server guy
- self = .foo(try singleValueContainer.decode(Foo.self))
- case "bar":
- self = .bar(try singleValueContainer.decode(Bar.self))
- case "baz":
- self = .baz(try singleValueContainer.decode(Baz.self))
- default:
- // this tag is unknown,or known but we don't care
- self = .unknown
- }
- }
- }
现在您可以解码以下JSON:
- let json: Data! = """
- [
- {"tag": "foo","fooValue": 1},{"tag": "bar","barValue": 2},{"tag": "baz","bazValue": 3}
- ]
- """.data(using: .utf8)
像这样:
- let taggedThings = try? JSONDecoder().decode([TagggedThing].self,from: json)