Search code examples
jsonswiftdictionarymodeldecoding

Unable to Parse this JSON in Swift


I'm Unable to Parse this JSON,

{
  "Teams": {
    "4": {
      "Name_Full": "India",
      "Players": {
        "3632": {
          "Name_Full": "Dinesh Karthik",
          "Batting": {                
            "Runs": "1738"
          },
          "Bowling": {
            "Wickets": "0"
          }
        }
      }
    },
    "5": {
      "Name_Full": "New Zealand",
      "Players": {
        "3752": {
          "Position": "4",
          "Name_Full": "Ross Taylor",
          "Batting": {                
            "Runs": "7890"
          },
          "Bowling": {                
            "Wickets": "0"
          }
        }
      }
    }
  }
}

I have tried creating Model Like this, but I'm getting error for naming "4" and "5"

struct CricketTeam: Codable {
    var Teams: Teams
}

struct Teams: Codable {
    var teamID: TeamID
}

struct TeamID: Codable {
    var teamPlayers: TeamPlayers
}

struct TeamPlayers: Codable {
    var Name_Full: String
    var playerID: PlayerID
}

struct PlayerID: Codable {
    var Name_Full: String
}

Is my Model wrong? I need to show a list contains Players Info, Like Name and Batting and Bowling Skills


Solution

  • Is my Model wrong?

    Yes. Starting from the top:

    Your top level object is a JSON object with one property called Teams

    struct Everything: Decodable
    {
        var Teams: ...
    }
    

    What is the type of that object? Well, it is also a JSON object keyed by a number (written as a string), and "Teams" implies a collection of teams, so we'll go for a Swift dictionary of a new type called Team

    struct Everything: Decodable
    {
        var Teams: [String : Team]
    }
    

    So what does a Team look like? Let's have a look at the JSON we are trying to decode.

    {
        "Name_Full": "India",
        "Players": {
            "3632": {
              "Name_Full": "Dinesh Karthik",
              "Batting": {                
                "Runs": "1738"
              },
              "Bowling": {
                "Wickets": "0"
              }
            }
        }
    }
    

    So a Team has a name and a collection of players. Thus, the team should look something like this.

    struct Team: Decodable
    {
        var Name_Full: String
        var Players: [String : Player]
    }
    

    We can go through a similar exercise for the Player type. This is the JSON for a player

    {
        "Position": "4",
        "Name_Full": "Ross Taylor",
        "Batting": {
            "Runs": "7890"
        },
        "Bowling": {
            "Wickets": "0"
        }
    }
    

    We have two simple properties and two properties that are JSON objects, so we'll create a Player struct, a Bowling struct and a Batting struct. Our final complete model looks like this.

    struct Evertything: Decodable
    {
        var Teams: [String : Team]
    }
    
    struct Team: Decodable
    {
        var Name_Full: String
        var Players: [String : Player]
    }
    
    
    struct Player: Decodable
    {
        var Name_Full : String
        var Batting : Batting
        var Bowling : Bowling
        var Position: String?
    }
    
    struct Batting: Decodable
    {
        var Runs : String
    }
    
    struct Bowling: Decodable
    {
        var Wickets : String
    }
    

    This is sufficient to deserialise the JSON from the question. The only issue now is that the ids for a player and a team exist only as dictionary keys. An individual Team or Player doesn't know its id. This is a bit tricky because putting the id in the Player and keeping the collection as a Dictionary leads us to potential logical inconsistencies. However, we can do it with a custom init(from:)

    Here's a way to do it. Each Team has an id and the dictionary of teams is transformed to an array to avoid the potential logical inconsistency. Since we need coding keys, I have taken the opportunity to create coding keys that translate the JSON keys to Swift conventional property names.

    struct Everything: Decodable
    {
        enum CodingKeys: String, CodingKey
        {
            case teams = "Teams"
        }
    
        init(teams: [Team])
        {
            self.teams = teams
        }
        var teams: [Team]
    
        init(from decoder: Decoder) throws
        {
            let container = try decoder.container(keyedBy: CodingKeys.self)
            var teamsDict = try container.decode([String : Team].self, forKey: .teams)
            for key in teamsDict.keys
            {
                teamsDict[key]?.id = key
            }
            self.init(teams: Array(teamsDict.values))
        }
    }
    
    struct Team: Decodable
    {
        enum CodingKeys: String, CodingKey
        {
            case name = "Name_Full"
            case players = "Players"
        }
        var id: String = ""
        var name: String
        var players: [String : Player]
    }
    

    You should do a similar trick with the Players property of a Team