问题描述
我正在尝试在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
属性的Player
对象数组,因此我使用了自定义初始化程序扩展了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响应未包含我进行完全初始化所需的所有信息,因此我应该如何进行:
- 我能否/应该进行两个API调用,一个对
/games
,另一个对players
,并以某种方式在解码之前合并它们? - 我是否应该仅部分初始化
Player
(将未知内容留给nil
)并稍后填充详细信息? (听起来很危险而且麻烦。) - 还有什么?
如果要尝试使用它,可以找到完整的示例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{ $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,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
实例。如果不是,则必须声明player1
和player2
为可选,并删除感叹号。