UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: Decoding dependent JSON data

Forums > Swift

Hi all! ๐Ÿ‘‹

It's my first post here, I'm a Python developer trying to learn Swift and already having issues ๐Ÿ™‚ I'm trying to decode "dependent" JSON API responses: let's imagine a fictional API with two endpoints:

  • /players, returns an array of objects with following attributes:
    • id, an integer representing the player ID
    • name, a string representing the player name
  • /games, returns an array of objects with following attributes:
    • name, a string representing the name of the game
    • playerId1, an integer representing the ID of the first player
    • playerId2, an integer representing the ID of the second player

I model each type with a 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"
    }
}

I want to decode the response from /games into an array of Game objects, with correct Players attributes, so I extended Game with a custom initializer but I don't know how to retrieve all player attributes:

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)
    }
}

I also tried another custom initializer with an extra parameter but didn't find how I could call it:

extension Game {
    init(from decoder: Decoder, allPlayers: [Player]) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)

        name = try values.decode(String.self, forKey: .name)

        playerId1 = try values.decode(Int.self, forKey: .player1)
        playerId2 = try values.decode(Int.self, forKey: .player2)

        player1 = allPlayers.first{ $0.id == playerId1 }
        player2 = allPlayers.first{ $0.id == playerId2 }
    }
}

To summarize, the API response from /games does not contain all the information I need for full initialization, so how should I proceed:

  • can/should I make two API calls, one to /games and another one to players and somehow merge them before decoding?
  • should I initialize my Players only partly (leaving unknown stuff to nil) and fill the details later? (That sound dangerous and cumbersome.)
  • anything else?

One thing to keep in mind is that the code making the API calls and instantiating my Games and Players will be in my ViewModel so I can't the list of players from my Model code for instance. I asked the same question on StackOverflow and the only answer at the moment suggests using a global variable players containing all Players in the model code but that's not possible (although it was my fault not making it clear in the question there)

Example code, including dummy API responses, can be found here.

Sorry for the long post and thanks in advance for any help ๐Ÿ™

2      

Hi!

What about not mapping players to game during decoding JSON but later when you need it?

You could (if it fits your app) create Players Dictionary where the key would be player id and when you need to get player name or the whole object, you can take id from the Game and get correct player from the dictionary.

2      

@nemecek-filip Thanks for your answer!

If I understand you correctly, that would mean making the player1 and player2 attributes of the Game class Optionals and initializing both of them to nil when doing the JSON decoding. I would then instantiate my Game and afterwards manually assign player1 and player2 from an array of [Player] retrieved with another API call. Is that right?

I'm just afraid that choosing this solution will be cumbersome because of all the Optional unwrapping afterwards and was hoping there might be a better solution ๐Ÿ˜•

2      

I meant more like keeping player1Id and player2Id properties on the Game and then using them to query the player once you need it.

2      

I see, that makes sense!

Meanwhile, I got another answer on StackOverflow and it looks more "Swifty" to me. The idea is to extend JSONDecoder with a custom convenience init and pass my players array there. Then, implement a custom init(from decoder: Decoder) on the Game class and try to retrieve the players array there.

If you're interested in the full implementation details, please check the accepted answer on my StackOverflow question ๐Ÿ™‚

2      

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills โ€“ itโ€™s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.