首页 > 解决方案 > 具有依赖类型的 Swift JSON 解码

问题描述

我正在尝试在 Swift 中解码“依赖”的 JSON API 响应。让我们想象一个具有两个端点的虚构 API:

我用 Swift 为每种类型建模struct

struct Player: Decodable {
    var id: Int
    var name: String?
}

struct Game: Decodable {
    var name: String
    var player1: Player
    var player2: Player
    
    enum CodingKeys: String, CodingKey {
        case name
        case player1 = "playerId1"
        case player2 = "playerId2"
    }
}

我想将响应解码为具有正确s 属性/games的对象数组,因此我使用自定义初始化程序进行了扩展,但我不知道如何检索所有玩家属性:GamePlayerGame

extension Game {
    
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        
        name = try values.decode(String.self, forKey: .name)
        
        //                                             HOW SHOULD I RETRIEVE THE PLAYER'S NAME GIVEN THEIR ID HERE?
        //                                                                         |
        //                                                                         |
        //                                                                         V
        player1 = Player(id: try values.decode(Int.self, forKey: .player1), name: nil)
        player2 = Player(id: try values.decode(Int.self, forKey: .player2), name: nil)
    }
}

总而言之,来自的 API 响应/games不包含完全初始化所需的所有信息,所以我应该如何继续:

如果你想尝试一下,你可以在这里找到一个完整的例子

标签: jsonswiftdecodable

解决方案


我的建议是添加两个惰性实例化属性以Player从数组中获取实例。

惰性属性相对于计算属性的好处是该值只计算一次,直到第一次被访问。init(from:)并且不需要自定义方法。

struct Game: Decodable {
    let name: String
    let playerId1: Int
    let playerId2: Int

    enum CodingKeys: String, CodingKey { case name, playerId1, playerId2 }

    lazy var player1 : Player? = players.first{ $0.id == playerId1 }
    lazy var player2 : Player? = players.first{ $0.id == playerId2 }
   
}

或者创建一个CodingUserInfoKey

extension CodingUserInfoKey {
    static let players = CodingUserInfoKey(rawValue: "players")!
}

和扩展JSONDecoder

extension JSONDecoder {
    convenience init(players: [Player]) {
        self.init()
        self.userInfo[.players] = players
    }
}

并在 JSON 解码器的对象中传递players数组userInfo

let decoder = JSONDecoder(players: players)
let games = try! decoder.decode([Game].self, from: Data(gamesResponse.utf8))
dump(games[0].player1)

现在您可以在方法中获取实际的玩家init(from:

struct Game: Decodable {
    let name: String
    let player1: Player
    let player2: Player
    
    enum CodingKeys: String, CodingKey {
        case name, playerId1, playerId2
    }
}

extension Game {
    
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        guard let players = decoder.userInfo[.players] as? [Player] else { fatalError("No players array available") }
        name = try values.decode(String.self, forKey: .name)
        let playerId1 = try values.decode(Int.self, forKey: .playerId1)
        let playerId2 = try values.decode(Int.self, forKey: .playerId2)
        player1 = players.first{ $0.id == playerId1 }!
        player2 = players.first{ $0.id == playerId2 }!
    }
}

代码假定players数组包含与值Player对应的所有实例playerId。如果不是,那么您必须将player1and声明player2为可选并删除感叹号。


推荐阅读