Working with JSON in Swift

The following snippet shows how you can convert raw data to a JSON dictionary. Working with JSON in Swift can be a little tedious at times, but overall, it's an alright experience. Let's look at an example:

guard let data = data,   
  let json = try? JSONSerialization.jsonObject(with: data, options: [])   
  else { return }   

print(json) 

The preceding snippet converts the raw data that is returned by a URL request to a JSON object. The print statement prints a readable version of the response data, but it's not quite ready to be used. Let's see how you gain access to the first available movie in the response.

If you look at the type of object returned by the jsonObject(with:options:) method, you'll see that it returns Any. This means that you must typecast the returned object to something you can work with, such as an array or a dictionary. When you inspect the JSON response that the API returned, for instance by using print to make it appear in the console like you did with Apple's homepage HTML, you'll notice that there's a dictionary that has a key called results. The results object is an array of movies. In other words, it's an array of [String: Any], because every movie is a dictionary, where strings are the keys and the value can be a couple of different things, such as Strings, Int, or Booleans. With this information, you can access the first movie's title in the JSON response, as shown in the following code:

guard let data = data,
  let json = try? JSONSerialization.jsonObject(with: data, options: []),
  let jsonDict = json as? [String: AnyObject],
  let resultsArray = jsonDict["results"] as? [[String: Any]]
  else { return }

let firstMovie = resultsArray[0]
let movieTitle = firstMovie["title"] as! String
print(movieTitle)

Working with dictionaries to handle JSON isn't the best experience. Since the JSON object is of the AnyObject type and you need to typecast every element in the dictionary you want to access, there's a lot of boilerplate code you need to add. Luckily, Swift has better ways to create instances of objects from the JSON data. The following example shows how you can quickly create an instance of a Movie struct without having to cast all the keys in the JSON dictionary to the correct types for the Movie struct.

First, let's define two structs, one for the Movie itself, and one for the response that contains the array of Movie instances:

struct MoviesResponse: Codable {
  let results: [Movie]
}

struct Movie: Codable {
  let id: Int
  let title: String
  let popularity: Float
}

Next, you can use the following snippet to quickly convert the raw data from a URL request to an instance of MoviesResponse, where all movies are converted to instances of the Movie struct:

let decoder = JSONDecoder()  
guard let data = data,  
    let movies = try? decoder.decode(MoviesResponse.self, from: data)  
    else { return }  

print(movies.results[0].title)

You might notice that both MoviesResponse and Movie conform to the Codable protocol. The Codable protocol was introduced in Swift 4, and it allows you to easily encode and decode data objects. The only requirement is that all properties of a Codable object conform to the Codable protocol. A lot of built-in types, such as Array, String, Int, Float, and Dictionary conform to Codable. Because of this, you can easily convert an encoded JSON object into a MoviesResponse instance that holds Movie instances.

By default, each property name should correspond to the key of the JSON response it is mapped to. However, sometimes you might want to customize this mapping. For instance, the poster_path property in the response we've been working with so far would be best mapped to a posterPath property on the Movie struct. The following example shows how you would tackle these circumstances:

struct Movie: Codable {  

    enum CodingKeys: String, CodingKey {  
        case id, title, popularity  
        case posterPath = "poster_path"  
    }  

    let id: Int  
    let title: String  
    let popularity: Float  
    let posterPath: String?  
}

By specifying a CodingKeys enum, you can override how the keys in the JSON response should be mapped to your Codable object. You must cover all keys that are mapped, including the ones you don't want to change. As you've seen, the Codable protocol provides powerful tools for working with data from the network. Custom key mapping makes this protocol even more powerful because it allows you to shape your objects exactly how you want them instead of having the URL responses dictate the structure to you.

If the only conversion you need to apply in the coding keys is converting from snake case (poster_path) to camel case (posterPath), you don't have to specify the coding keys yourself. The JSONEncoder object can automatically apply this type of conversion when decoding data if you set its keyDecodingStrategy to .convertFromSnakeCase, as shown in the following code:

let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase

Try implementing this in your playground and remove CodingKeys from the Movie object to ensure your JSON decoding still works.

Now let's move on to storing fetched data in the Core Data database.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.17.79.60