Working with JSON in Swift

Original author: Attila Hegedüs
  • Transfer
  • Tutorial
JavaScript Object Notation , or JSON for short, is the most common way to communicate with the server and get information from it. It is extremely popular because of its ease of use and perception.

Consider the following JSON snippet:
[
  {
    "person": {
      "name": "Dani",
      "age": "24"
    }
  },
  {
    "person": {
      "name": "ray",
      "age": "70"
    }
  }
]


In Objective-C, parsing and deserializing JSON is quite simple:
NSArray *json = [NSJSONSerialization JSONObjectWithData:JSONData options:kNilOptions error:nil];
NSString *age = json[0][@"person"][@"age"];
NSLog(@"Dani's age is %@", age);


In Swift, this is a more complex process due to the option types and type safety:
var json: Array!
do {
  json = try NSJSONSerialization.JSONObjectWithData(JSONData, options: NSJSONReadingOptions()) as? Array
} catch {
  print(error)
}
if let item = json[0] as? [String: AnyObject] {
  if let person = item["person"] as? [String: AnyObject] {
    if let age = person["age"] as? Int {
      print("Dani's age is \(age)")
    }
  }
}


In the code above, you must verify each object before using optional binding . This will protect your code; but the more complex JSON, the more cumbersome the code becomes.

In Swift 2.0, the guard statement was introduced in order to get rid of nested if statements:
guard let item = json[0] as? [String: AnyObject],
  let person = item["person"] as? [String: AnyObject],
  let age = person["age"] as? Int else {
    return;
}
print("Dani's age is \(age)")

Still verbose? But how to simplify this?

This JSON article uses the simplest way to parse JSON - using the popular public Gloss library .

So, in particular, you will use Gloss for parsing and JSON conversion, which contains 25 popular applications in the US App Store. As easy as in Objective-C!

Where to begin


Download the launch pad for this article.

Since the user interface is not needed, we will work exclusively with the playground.

Open  Swift.playground  in Xcode and learn it.

Note:  You may notice that Project Navigator is closed by default. If so, press Command + 1 to display it. You should succeed as in the image below.



The playground start file contains several source and resource files that are completely focused on JSON parsing. Pay attention to the structure of the playground:

  • The Resources folder contains resources that can be accessed through your Swift code
  • topapps.json : Contains a string for parsing JSON.
  • The Sources folder contains additional source files that your playground code has access to. Adding .swift support files  to this folder should be thought out, this will bring ease of reading to your playground.
  • App.swift : The structure of an old simple Swift is an application. Your goal is to parse JSON into a collection of objects.
  • DataManager.swift : Controls the extraction of data from a local network or the Internet. Use the methods of this file to load JSON later.


Once you understand this playground'e, continue to read the article!

The primary way to parse JSON in Swift


First, let's start with the native method of parsing JSON in Swift - that is, without using external libraries. With this, you will appreciate the benefits of using a library like Gloss.

Note:  If you have already studied the disadvantages of the native JSON parsing method and want to switch to Gloss, skip the next paragraph.

To give the name of the application # 1 on the App Store, we will analyze the provided JSON file.

Before you start working with dictionaries, specify an additional name (alias) at the top of the playground:

typealias Payload = [String: AnyObject]

Add the callback code getTopAppsDataFromFileWithSuccess as shown below:
DataManager.getTopAppsDataFromFileWithSuccess { (data) -> Void in
  var json: Payload!
  // 1
  do {
    json = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions()) as? Payload
  } catch {
    print(error)
    XCPlaygroundPage.currentPage.finishExecution()
  }
  // 2
  guard let feed = json["feed"] as? Payload,
    let apps = feed["entry"] as? [AnyObject],
    let app = apps.first as? Payload
    else { XCPlaygroundPage.currentPage.finishExecution() }
  guard let container = app["im:name"] as? Payload,
    let name = container["label"] as? String
    else { XCPlaygroundPage.currentPage.finishExecution() }
  guard let id = app["id"] as? Payload,
    let link = id["label"] as? String
    else { XCPlaygroundPage.currentPage.finishExecution() }
  // 3
  let entry = App(name: name, link: link)
  print(entry)
  XCPlaygroundPage.currentPage.finishExecution()
}


Here's what happens:
  1. You first deserialize the data using NSJSONSerialization.
  2. You must check each index value in the JSON object to prevent nil from appearing. Once a valid value is found, continue to search for the following objects. After going through all the indexes, you will get the name and link values ​​with which to work. Please note that if at least one JSON element is unexpected, the application name will not be displayed. It is advisable to handle in this case.
  3. The last step is to initialize the App object  using the name  and  link values  and output them to the console.

Save and run the playground; You will see the following in the debugger console:

App(name: "Game of War - Fire Age", link: "https://itunes.apple.com/us/app/game-of-war-fire-age/id667728512?mt=8&uo=2")


Yes - “Game of War - Fire Age” is application # 1 in the JSON file.

It took quite a lot of code to get the name of the first application - it's time to see how Gloss handles this .

Introducing Object Conversion with JSON


Object mapping is a technique for transforming objects from JSON into Swift objects. After defining model objects and mapping rules, Gloss does the hard work by parsing for you.

This method is much simpler than the one you used before:
  • Your code will be cleaner, it can be reused and, in turn, easier to maintain.
  • You work more with objects, rather than generalized complexes and dictionaries.
  • You can extend model classes to add additional features.

Sounds good, huh? Let's see how it works!

Parsing JSON with Gloss


To make everything look perfect, create a new playground Gloss.playground , then copy the topapps.json  file to the  Resources  and  DataManager.swift  folder and the Sources folder  .

Implement Gloss in the project


It’s quite simple to implement Gloss in your project:
  1. Click this Gloss Repo Zip File link  and save the library to a suitable location.
  2. Unzip it and drag the Gloss-master / Gloss / Gloss  folder into the Sources folder of  your playground.


Your Project Navigator should look like this:
Project navigator

That's it! Gloss is now added to your project and you can start parsing JSON without any headaches!

Note:  Gloss can also be installed via Cocoapods. Since the playground does not yet support them, this method can only be used when working with projects.

Convert JSON to Objects


First, find out how your object relates to your JSON document.

The object must be compliant with the Decodeable protocol, which can decode them with JSON. To do this, initialize init? (Json: JSON)  as the protocol says.

Pay attention to the structure of topapps.json and create a data model.

TopApps

TopApps is a top-level object that holds one key-value pair
{
  "feed": {
    ...
  }
}

Create a new file called TopApps.swift and place it in the Sources folder of   your playground; add the following code:
public struct TopApps: Decodable {
  // 1
  public let feed: Feed?
  // 2
  public init?(json: JSON) {
    feed = "feed" <~~ json
  }
}


  1. Define the parameters for the model. In this case, he will be alone. You will add a Feed object later .
  2. When implementing a custom initializer, verify that TopApps is compliant with the protocol. You must be wondering what <~~! This is the Encode Operator, which is defined in the Gloss'sOperators.swift file . Basically, this means that Gloss moves the values ​​that belong to the feed key and encrypts them (encode). Feed is also a Decodable; therefore, Gloss will transfer responsibility for encryption.

Feed
The Feed object is very similar to a top-level object. It has two key-value pairs, but since we are interested in only 25 popular applications, there is no need to process the author object.
{
  "author": {
    ...
  },
  "entry": [
    ...
  ]	
}


Create a new file called  Feed.swift  in the Sources folder of your playground and describe it as follows:
public struct Feed: Decodable {
  public let entries: [App]?
  public init?(json: JSON) {
    entries = "entry" <~~ json
  }
}


App
The most recent object to describe is the App object. The application is presented in the form of such a scheme:
{
  "im:name": {
    "label": "Game of War - Fire Age"
  },
  "id": {
    "label": "https://itunes.apple.com/us/app/game-of-war-fire-age/id667728512?mt=8&uo=2",	
    ...
  },
  ...
}


Create a new file called App.swift  in the Sources folder of your playground and add the following code:
public struct App: Decodable {
  // 1
  public let name: String
  public let link: String
  public init?(json: JSON) {
    // 2
    guard let container: JSON = "im:name" <~~ json,
      let id: JSON = "id" <~~ json
      else { return nil }
    guard let name: String = "label" <~~ container,
      link: String = "label" <~~ id
      else { return nil }
    self.name = name
    self.link = link
  }
}


  1. Both Feed and  TopApp  used optional properties. A property can be defined as non-optional only if the used JSON will always contain values ​​to populate them.
  2. It is not necessary to create an object for each component in JSON. For example, in this case it makes no sense to create a model for in: name and id. When working with non-optional and nested objects, do not forget to check nil .


Now that your classes are ready, it remains to let Gloss do the work!
Open the playground file and add the following code:
import UIKit
import XCPlayground
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
DataManager.getTopAppsDataFromFileWithSuccess { (data) -> Void in
  var json: [String: AnyObject]!
  // 1
  do {
    json = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions()) as? [String: AnyObject]
  } catch {
    print(error)
    XCPlaygroundPage.currentPage.finishExecution()
  }
  // 2
  guard let topApps = TopApps(json: json) else {
    print("Error initializing object")
    XCPlaygroundPage.currentPage.finishExecution()
  }
  // 3
  guard let firstItem = topApps.feed?.entries?.first else {
    print("No such item")
    XCPlaygroundPage.currentPage.finishExecution()
  }
  // 4
  print(firstItem)
  XCPlaygroundPage.currentPage.finishExecution()
}


  1. Deserialize the data first using NSJSONSerialization. We have already done this before.
  2. Initialize the TopApps object instance using data with JSON through the constructor.
  3. With the first input feed, get app # 1
  4. Print the app object to the console.


Seriously - this is all the code we need.

Save and run your playground; You again successfully received the name of the application, but in a more elegant way.
App(name: "Game of War - Fire Age", link: "https://itunes.apple.com/us/app/game-of-war-fire-age/id667728512?mt=8&uo=2")


All this relates to parsing local data. But what about parsing data from a remote resource?

Getting JSON from the network


It is time to make this project more real. Usually, you get data from a remote resource, not from local files. It is easy to get ratings from the App Store using a network request.

Open  DataManager.swift  and find the URL for the best apps:
let TopAppURL = "https://itunes.apple.com/us/rss/topgrossingipadapplications/limit=25/json"

Then add the following method to the DataManager implementation  :
public class func getTopAppsDataFromItunesWithSuccess(success: ((iTunesData: NSData!) -> Void)) {
  //1
  loadDataFromURL(NSURL(string: TopAppURL)!, completion:{(data, error) -> Void in
      //2
      if let data = data {
        //3
        success(iTunesData: data)
      }
  })
}


The code above looks pretty familiar; but instead of retrieving data from a local file, we used NSURLSession to retrieve data from iTunes. Here's what exactly happens:

  1. First you call the loadDataFromURL method; it requires a URL and a loopback function that passes an NSData object.
  2. Using an additional binding, we verify the existence of data.
  3. Ultimately, you commit data to success, as you did before.


Open your playground and replace the following code:
DataManager.getTopAppsDataFromFileWithSuccess { (data) -> Void in

on this
DataManager.getTopAppsDataFromItunesWithSuccess { (data) -> Void in


You have now received real data from iTunes.

Save and run your playground; you will see that parsing the information still leads to the same final result:

App(name: "Game of War - Fire Age", link: "https://itunes.apple.com/us/app/game-of-war-fire-age/id667728512?mt=8&uo=2")


The value above may vary as the popular App Store apps are constantly changing.

Often people are not interested only in the TOP apps of the App Store - they want to see a list of all the TOP apps. No need to code to get them. Just add the following code snippet:
topApps.feed?.entries


Gloss - skeletons in the closet


It's not hard to notice that Gloss does a wonderful job of parsing - but what is behind it? <~~ is a custom operator for a number of Decoder.decode functions. Gloss has built-in support for decoding many types:
  • Simple types (Decoder.decode)
  • Decodable Models (Decoder.decodeDecodable)
  • Simple arrays (Decoder.decode)
  • Arrays and Decodable Models (Decoder.decodeDecodableArray)
  • Enum (Decoder.decodeEnum)
  • Enum Arrays (Decoder.decodeEnumArray)
  • NSURL (Decoder.decodeURL)
  • NSURL Arrays (Decode.decodeURLArray)


Note: If you want to learn more about Custom Operators , see the article: Operator Overloading in Swift Tutorial

In this article, we looked at decoded models. If you need something more complicated, expand Decoder and make your own implementation of decoding.

Of course, with Gloss you can also convert objects to JSON. If you're interested, check out the Encodable protocol.

What's next?


Here is the final playground .

You can use the playground as an initial step to create a new application; just replace the data receiving URL remotely with your own URL, manage your own keys and indexes for your new JSON, and you will create a new project that will parse something, for example, the results of football matches or other data received from the network.

Gloss development continues here  on Github, so stay tuned for the latest updates.

Swift is still evolving, so you should keep an eye on Apple’s new documentation for future language updates.

I hope you enjoyed this article about working with JSON. If you have questions or comments, you can discuss them below!

Also popular now: