Swift JSON decoding with dependent types
My suggestion is to add two lazy instantiated properties to get the Player
instances from the array.
The benefit of a lazy property over a computed property is that the value is calculated once and not until it's being accessed the first time. And a custom init(from:)
method is not needed.
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 }
}
Alternatively create a CodingUserInfoKey
extension CodingUserInfoKey {
static let players = CodingUserInfoKey(rawValue: "players")!
}
and an extension of JSONDecoder
extension JSONDecoder {
convenience init(players: [Player]) {
self.init()
self.userInfo[.players] = players
}
}
and pass the players
array in the userInfo
object of the JSON decoder
let decoder = JSONDecoder(players: players)
let games = try! decoder.decode([Game].self, from: Data(gamesResponse.utf8))
dump(games[0].player1)
Now you can get the actual players in the init(from:
method.
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 }!
}
}
The code assumes that the players
array contains all Player
instances corresponding to the playerId
values. If not then you have to declare player1
and player2
as optional and remove the exclamation marks.
Generic Decoder for Swift using a protocol
A protocol cannot conform to itself, Codable
must be a concrete type or can only be used as a generic constraint.
In your context you have to do the latter, something like this
func fetch<T: Decodable>(with request: URLRequest, decode: @escaping (Data) throws -> T, completion: @escaping (Result<T, APIError>) -> Void) { }
func getData<T: Decodable>(_ : T.Type = T.self, from endPoint: Endpoint, completion: @escaping (Result<T, APIError>) -> Void) {
let request = endPoint.request
fetch(with: request, decode: { data -> T in
return try JSONDecoder().decode(T.self, from: data)
}, completion: completion)
}
A network request usually returns Data
which is more reasonable as parameter type of the decode
closure
Decodable wrapper for standard nested JSON in swift
In this specific case, you have multiple User
objects, so you'd need some kind of container:
struct Users: Decodable {
var users: [User]
}
User
also doesn't need to be this complicated:
struct User: Decodable {
var id: String
var firstName: String
var lastName: String
var email: String
}
and then decode as:
let users = JSONDecoder().decode(JSONResponse<Users>.self, from: responseData)
Related Topics
How to Use the Optional Variable in a Ternary Conditional Operator
Using the Swift If Let with Logical and Operator &&
Swift Enum with Custom Initializer Loses Rawvalue Initializer
How Does One Use Nsdateformatter's Islenient Option
"Ambiguous Use of 'Children'" When Trying to Use Nstreecontroller.Arrangedobjects in Swift 3.0
Simple Swift Class Does Not Compile
What Is the Shortest Way to Run Same Code N Times in Swift
How to Change Uitextfield Keyboard Type to Email in Swift
Composing Video and Audio - Video's Audio Is Gone
Randomize Two Arrays the Same Way Swift
Using @Viewbuilder to Create Views Which Support Multiple Children
Color Ouput with Swift Command Line Tool
Can't Import Packages Using Swift 4 Package Manager
Why Is My Sound Making My Game Lag in Swift Spritekit
Including Zeros in Front of an Integer
Equivalent of Skaction Scaletox for a Given Duration in Unity