Swift Decodable, Endpoint Returns Completely Different Types

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



Leave a reply



Submit