我有一个带有数组值的JSON:
[
{ "tag": "Foo", … },
{ "tag": "Bar", … },
{ "tag": "Baz", … },
]
我想将此数组解码为struct
的数组,其中特定类型取决于标记:
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 }
有更好的方法吗?
答案 0 :(得分:4)
您的数组包含有限,可枚举变种的异构对象;听起来像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
}
您的数组可以包含上述类型的任何实例,或者包含未知类型的实例。所以你有枚举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)
答案 1 :(得分:3)
可能是枚举可以使您的代码更清洁。每个案例都对应于json的类型(标记)。根据具体情况,您将json解析为适当的模型。无论如何应该有某种评估模型可供选择。所以我来到了这个
protocol SomeCommonType {}
protocol DecodableCustomType: Decodable, SomeCommonType {}
struct Foo: DecodableCustomType {}
struct Bar: DecodableCustomType {}
struct Baz: DecodableCustomType {}
enum ModelType: String {
case foo
case bar
case baz
var type: DecodableCustomType.Type {
switch self {
case .foo: return Foo.self
case .bar: return Bar.self
case .baz: return Baz.self
}
}
}
func decoder(json: JSON) {
let type = json["type"].stringValue
guard let modelType = ModelType(rawValue: type) else { return }
// here you can use modelType.type
}
答案 2 :(得分:1)
您还可以使用字典进行映射:
protocol SomeCommonType {}
struct Foo: Decodable, SomeCommonType { }
struct Bar: Decodable, SomeCommonType { }
struct Baz: Decodable, SomeCommonType { }
let j: [[String:String]] = [
["tag": "Foo"],
["tag": "Bar"],
["tag": "Baz"],
["tag": "Undefined type"],
["missing": "tag"]
]
let mapping: [String: SomeCommonType.Type] = [
"Foo": Foo.self,
"Bar": Bar.self,
"Baz": Baz.self
]
print(j.map { $0["tag"].flatMap { mapping[$0] } })
// [Optional(Foo), Optional(Bar), Optional(Baz), nil, nil]
print(j.flatMap { $0["tag"].flatMap { mapping[$0] } })
// [Foo, Bar, Baz]