Swift JSON 使用依赖类型解码
Swift JSON decoding with dependent types
我正在尝试解码 Swift 中的“依赖”JSON API 响应。让我们想象一个虚构的 API 有两个端点:
/players
, returns 具有以下属性的对象数组:
id
,代表玩家ID的整数
name
,代表玩家名字的字符串
/games
, returns 具有以下属性的对象数组:
name
,代表游戏名称的字符串
playerId1
,代表第一个玩家ID的整数
playerId2
,代表第二个玩家ID的整数
我用 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
s 属性,所以我使用自定义初始化器扩展了 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 响应不包含完全初始化所需的所有信息,所以我应该如何进行:
- can/should 我打了两个 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{ [=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
个实例。如果不是,那么您必须将 player1
和 player2
声明为可选的并删除感叹号。
我正在尝试解码 Swift 中的“依赖”JSON API 响应。让我们想象一个虚构的 API 有两个端点:
/players
, returns 具有以下属性的对象数组:id
,代表玩家ID的整数name
,代表玩家名字的字符串
/games
, returns 具有以下属性的对象数组:name
,代表游戏名称的字符串playerId1
,代表第一个玩家ID的整数playerId2
,代表第二个玩家ID的整数
我用 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
s 属性,所以我使用自定义初始化器扩展了 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 响应不包含完全初始化所需的所有信息,所以我应该如何进行:
- can/should 我打了两个 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{ [=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
个实例。如果不是,那么您必须将 player1
和 player2
声明为可选的并删除感叹号。