无法将符合协议的元素映射到通用元素

Cannot map protocol compliant elements to generic elements

正如您在下面看到的,我下载了一个包含异构对象的结构数组,这些对象被解码为包含嵌套对象的枚举。

我现在想将所述对象放入通用模型结构中,但编译器不允许这样做 - 错误在下面的代码注释中描述。我对 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)
}

这里不需要泛型。

更改模型以接受任何符合初始化中 Displayable 的类型

struct Model {
    let text: String
    let image: URL

    init(item: Displayable) {
        self.text = item.text
        self.image = item.image
    }
}

然后把闭包改成return模型

let models = items.map { (item) -> Model in

如果您想保持模型结构的通用性,则需要将 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 type:Model<League>, Kawhi Leonard type:Model<Player>, Los Angeles Clippers type:Model<Team>]

如果您只对 name 和表示 URL 的键感兴趣,请使用 nestedContainer 以这种方式将 JSON 直接解析为 Model

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)
}