Swift JSON 使用依赖类型解码

Swift JSON decoding with dependent types

我正在尝试解码 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"
    }
}

我想将来自 /games 的响应解码为 Game 对象数组,具有正确的 Players 属性,所以我使用自定义初始化器扩展了 Game但我不知道如何检索所有播放器属性:

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

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

如果你想试验一下,你可以找到一个完整的例子here

我的建议是添加两个惰性实例化属性以从数组中获取 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{ [=10=].id == playerId1 }
    lazy var player2 : Player? = players.first{ [=10=].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解码器

userInfo对象中传递players数组
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{ [=14=].id == playerId1 }!
        player2 = players.first{ [=14=].id == playerId2 }!
    }
}

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