问题描述
如下所示,我下载了一个包含异构对象的结构数组,这些对象被解码为包含嵌套对象的枚举。
我现在想将所述对象放入通用模型结构中,但编译器不允许这样做 - 下面的代码注释中描述了错误。我对 Swift 编程比较陌生,非常感谢您的帮助。
import Foundation
let jsonString = """
{
"data":[
{
"type":"league","info":{
"name":"NBA","sport":"Basketball","website":"https://nba.com/"
}
},{
"type":"player","info":{
"name":"Kawhi Leonard","position":"Small Forward","picture":"https://i.ibb.co/b5sGk6L/40a233a203be2a30e6d50501a73d3a0a8ccc131fv2-128.jpg"
}
},{
"type":"team","info":{
"name":"Los Angeles Clippers","state":"California","logo":"https://logos-download.com/wp-content/uploads/2016/04/LA_Clippers_logo_logotype_emblem.png"
}
}
]
}
"""
struct Response: Decodable {
let data: [Datum]
}
struct League: Codable {
let name: String
let sport: String
let website: URL
}
extension League: displayable {
var text: String { name }
var image: URL { website }
}
struct Player: Codable {
let name: String
let position: String
let picture: URL
}
extension Player: displayable {
var text: String { name }
var image: URL { picture }
}
struct Team: Codable {
let name: String
let state: String
let logo: URL
}
extension Team: displayable {
var text: String { name }
var image: URL { logo }
}
enum Datum: Decodable {
case league(League)
case player(Player)
case team(Team)
enum DatumType: String,Decodable {
case league
case player
case team
}
private enum CodingKeys : String,CodingKey { case type,info }
init(from decoder : Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(DatumType.self,forKey: .type)
switch type {
case .league:
let item = try container.decode(League.self,forKey: .info)
self = .league(item)
case .player:
let item = try container.decode(Player.self,forKey: .info)
self = .player(item)
case .team:
let item = try container.decode(Team.self,forKey: .info)
self = .team(item)
}
}
}
protocol displayable {
var text: String { get }
var image: URL { get }
}
struct Model<T: displayable> {
let text: String
let image: URL
init(item: T) {
self.text = item.text
self.image = item.image
}
}
do {
let response = try JSONDecoder().decode(Response.self,from: Data(jsonString.utf8))
let items = response.data
let models = items.map { (item) -> Model<displayable> in // error: only struct/enum/class types can conform to protocols
switch item {
case .league(let league):
return Model(item: league)
case .player(let player):
return Model(item: player)
case .team(let team):
return Model(item: team)
}
}
} catch {
print(error)
}
解决方法
这里不需要泛型。
更改Model以接受init中符合Displayable的任何类型
struct Model {
let text: String
let image: URL
init(item: Displayable) {
self.text = item.text
self.image = item.image
}
}
然后改变闭包返回Model
let models = items.map { (item) -> Model in
如果您想保持 Model 结构通用,则需要将 map
调用更改为
let models: [Any] = items.map { item -> Any in
switch item {
case .league(let league):
return Model(item: league)
case .player(let player):
return Model(item: player)
case .team(let team):
return Model(item: team)
}
}
当符合 CustomStringConvertible
extension Model: CustomStringConvertible {
var description: String {
"\(text) type:\(type(of: self))"
}
}
print(models)
[NBA 类型:模特,科怀伦纳德类型:模特,洛杉矶快船类型:模特]
,如果您只对 name
和表示 URL 的键感兴趣,可以通过这种方式使用 Model
将 JSON 直接解析为 nestedContainer
struct Response: Decodable {
let data: [Model]
}
struct Model: Decodable {
let name: String
let image: URL
enum DatumType: String,Decodable {
case league
case player
case team
}
private enum CodingKeys : String,CodingKey { case type,info }
private enum CodingSubKeys : String,CodingKey { case name,website,picture,logo }
init(from decoder : Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(DatumType.self,forKey: .type)
let subContainer = try container.nestedContainer(keyedBy: CodingSubKeys.self,forKey: .info)
self.name = try subContainer.decode(String.self,forKey: .name)
let urlKey : CodingSubKeys
switch type {
case .league: urlKey = .website
case .player: urlKey = .picture
case .team: urlKey = .logo
}
self.image = try subContainer.decode(URL.self,forKey: urlKey)
}
}
do {
let response = try JSONDecoder().decode(Response.self,from: Data(jsonString.utf8))
let data = response.data
print(data)
} catch {
print(error)
}