json - 具有依赖类型的 Swift JSON 解码
问题描述
我正在尝试在 Swift 中解码“依赖”的 JSON API 响应。让我们想象一个具有两个端点的虚构 API:
/players
,返回具有以下属性的对象数组:id
, 表示玩家 ID 的整数name
, 代表玩家姓名的字符串
/games
,返回具有以下属性的对象数组:name
, 表示游戏名称的字符串playerId1
, 一个整数,表示第一个玩家的 IDplayerId2
, 一个整数,表示第二个玩家的 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"
}
}
我想将响应解码为具有正确s 属性/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)
}
}
总而言之,来自的 API 响应/games
不包含完全初始化所需的所有信息,所以我应该如何继续:
- 我可以/应该进行两次 API 调用,一次调用,
/games
另一次调用,players
并在解码之前以某种方式合并它们? - 我应该只部分初始化我
Player
的 s (留下未知的东西nil
)并稍后填写详细信息吗?(这听起来既危险又麻烦。) - 还要别的吗?
如果你想尝试一下,你可以在这里找到一个完整的例子
解决方案
我的建议是添加两个惰性实例化属性以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
。如果不是,那么您必须将player1
and声明player2
为可选并删除感叹号。
推荐阅读
- regex - 任何字符的正则表达式,至少 5 个长度,[.] 不起作用
- c++ - 如何初始化对象数组?
- excel - 如果该文件夹已经存在,则创建一个文件夹 创建一个子文件夹 VBA
- reactjs - 可以一起使用 styled-components 和 react-redux 连接,withTheme HOC 没有这种糟糕的解决方法吗?
- awk - 使用 AWK 添加评论
- azure - Azure E 系列(E20S_V3 和 E20DS_V4)
- python - 自定义融化/折叠熊猫
- css - 有没有办法为 FluentUI 下拉控件自定义悬停样式?
- flutter - 下拉按钮值在网格视图颤动的每个项目中都发生了变化
- azure-cloud-shell - Azure Cloud Shell ssh 密钥持久性