Stuart Breckenridge

Simple JSON Parsing with Codable

As I rewrite Amazing Flag Quiz for iOS 11, one of the greatest things I’ve come across is the new Codable protocol in Swift 4. Before I get into why it’s great, here’s a bit of background: Amazing Flag Quiz v1 to v2.1.2 (current) contains an XML file of ids, country names (short and long, I don’t know why!), continent names, flag image names, and population (I don’t know why!). That file looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<countries>
  <country>
    <ID>0</ID>
    <shortCountryName>Afghanistan</shortCountryName>
    <longCountryName>Afghanistan</longCountryName>
    <countryFlag>Afghanistan.png</countryFlag>
    <countryPopulation>25500100</countryPopulation>
    <countryContinent>Asia</countryContinent>
  </country>
  <country>
    <ID>1</ID>
    <shortCountryName>Aland</shortCountryName>
    <longCountryName>'c5land Islands</longCountryName>
    <countryFlag>Aland.png</countryFlag>
    <countryPopulation>28355</countryPopulation>
    <countryContinent>Don't Use</countryContinent>
  </country>
  ...
</countries>

To use this file meant creating an NSXMLParser and all the code that goes along with it. For version 3 of Amazing Flag Quiz, I’ve decided to convert the XML to JSON and use a Country struct that conforms to Decodable.

The tidied up JSON file looks like this:

[
  {
  "name": "Afghanistan",
  "game": "Asia",
  "excludeFromWorldChallenge": false,
  "flag": "AF.png"
  },
  {
  "name": "Albania",
  "game": "Europe",
  "excludeFromWorldChallenge": false,
  "flag": "AL.png"
  }, 
  ...
]

The Country struct mirrors the data set:

/// Representation of data as held in the countryFlags.json file.
struct Country: Decodable {
    var name: String
    var flag: String
    var game: String
    var excludeFromWorldChallenge: Bool
}

I then have a single function to extract a Country array for the game type the player picks:

func flags(for gameType: GameType?) -> [Country] {
    
    let decoder = JSONDecoder()
    let data = try? Data(contentsOf: file!)
    let decodedData = try? decoder.decode([Country].self, from: data!)
    
    guard let countries = decodedData else {
        return []
    }
    
    if gameType == nil {
        return countries
    }
    
    if gameType == GameType.world {
        return GKRandomSource.sharedRandom().arrayByShufflingObjects(in: countries.filter({ $0.excludeFromWorldChallenge == false })) as! [Country]
    }
    
    return  GKRandomSource.sharedRandom().arrayByShufflingObjects(in: countries.filter({ $0.game == gameType!.rawValue })) as! [Country]

}

If a player selected the Asia game type:

let gameArray = flags(for: .asia) // returns a shuffled array of Asian countries.

Codable has made JSON parsing so simple. I’d recommend the Ulimate Guide to JSON Parsing With Swift 4 by Ben Scheirman as further reading.


Supported by