Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

All You Need to Know About JSON With Swift

DZone's Guide to

All You Need to Know About JSON With Swift

JSON is easy for humans to read and write and easy for machines to parse and generate. It can work with Swift to reduce the amount of code tremendously.

· Mobile Zone
Free Resource

Download this comprehensive Mobile Testing Reference Guide to help prioritize which mobile devices and OSs to test against, brought to you in partnership with Sauce Labs.

JSON stands for JavaScript Object Notation. It’s the de facto standard for server-client data communication. In this post, we will go over a few concepts about data and demo using JSON in a Xcode Playground. You are encouraged to try out and modify examples.

JSON Logo

A Word on Data and Information

Applications manipulate data and present it to the user as information.

There’s a subtle distinction involved:

  • Data is a collection of symbols on which operations are performed usually by computers. Data is quantitative, it takes up storage such as 3 kilobytes. For example, a piece of data is “June 2, 2014”, a date.
  • Information is the specific meaning of data. For example, the date of “June 2, 2014” represents the Swift language release date.

As civilization’s data storage needs and solutions progressed from clay tablets through paper to electronics over thousands of years, computer scientists improved on ways to store and handle massive amounts of data over the course of a few decades.

Common Formats

Data storage formats tend to be specialized for their own so called problem domain. For each kind of data you have at least one way of efficiently representing it.

The JPG format is well suited for images captured by cameras’ sensors whereas PNG is better suited for designed images. The same parallels could be dotted between the MP3 format and OGG sound format.

Databases are used for large quantities of data. They use:

  • Tables, which store data.
  • Schemas, which define the kind of information stored in a Table.
  • Queries, which are used to retrieve or manipulate data from tables.
  • Views, which hold results of queries.

SQL is the most used database management system and several providers offer implementations for it. We mentioned this here, because working with data we need to be aware notions, ask ourselves and answer some of the same questions as database developers. According to ANSI (American National Standards Institute), it is the standard language for relational database management systems.

Let’s consider a blob of data in our human minds:

We want to describe some of the Swift's programming language facts.
It was designed by Apple Inc.
It first appeared on June 2, 2014
It's typing is static, strong and inferred
It runs on Mac OS, Linux, FreeBSD
It uses the Apache License 2.0
Website: swift.org

We would like to be able to transmit or store this information in a way that we can write code that interacts with it.

Custom Format

You can invent and implement you own custom data format. Although the pride factor of inventing and implementing your own format would be very high, the usefulness of it would be less than limited, only the author’s software would be able to read and write from it.

An example of a custom format representing the information about swift:

I HAS A VAR ITZ Swift
I HAS A VAR ITZ Developer = Apple Inc.
BTW VAR Release::June 02, 2014
I HAS Typing -> static, strong, inferred
...
K THANKS BYE!

Apart from the strong influence from LOLCode, it looks like special handling would need to be implemented to handle this format. The values aren’t even clearly separated in it.

One would fare much better adhering to a standard format. Your job isn’t to reinvent the wheel, it’s to know what kinds of wheels exists and which works best for your current situation.

A Bit About Standards

Standards are conventions over how specific tasks should be done so multiple groups of people can create interoperable parts. Creating a component that can interface with other standard parts saves a lot of implementation time and opens doors for interoperability with other systems.

For example take the USB standard, short for Universal Serial Bus, is an industry standard initially developed in the mid-90s that defines the cables, connectors, and communications protocols used in a bus for connection, communication, and power supply between computers and electronic devices.

Because the USB is a technical standard, anybody can add a USB port to their product, anybody can manufacture ports or cables, and you can use any combination of them together.

While some standards are about mechanical components, others are for aerospace engineering, computers, communication and data transfer protocols, food, chemicals, etc.

A technical standard is an established norm or requirement in regard to technical systems. It is usually a formal document that establishes uniform engineering or technical criteria, methods, processes and practices.

In contrast, a custom, convention, company product, corporate standard, and so forth that becomes generally accepted and dominant is often called a de facto standard.

A technical standard may be developed privately or unilaterally, for example by a corporation, regulatory body, military, etc. Standards can also be developed by groups such as trade unions, and trade associations. Standards organizations often have more diverse input and usually develop voluntary standards: these might become mandatory if adopted by a government (i.e. through legislation), business contract, etc. (ex. power supply and sockets)

Use of the standards aids in the creation of products and services that are safe, reliable and of good quality. They help businesses increase productivity while minimizing errors and waste. By enabling products from different markets to be directly compared, they facilitate companies in entering new markets and assist in the development of global trade on a fair basis.

The most know standards organization is ISO, the International Organization for Standardization. ISO is an international standard-setting body composed of representatives from various national standards organizations. It is headquartered in Geneva, Switzerland, and as of 2015 works in 163 countries.

CSV

CSV is a text file of comma separated values or any other character, actually. This is known as the separator:

Name, Designer, Release Date, Typing, OS, License, Website
Swift, Apple Inc., June 2 2014, static; strong; inferred, Mac OS; Linux; FreeBSD, Apache License 2.0, swift.org

This looks a lot better, each value corresponds to a column. They are all separated by a comma , character. A ; is used to separate multiple values in the same column. We can’t really say it’s very legible, we can’t say that the type of value is contained in it, but it can be worked with.

XML

XML used to be the de facto standard for server-client data communication. It’s the X in AJAX.

XML stands for eXtensible Markup Language. It was designed to store and transport data while being both human and machine-readable.

You may know of HTML, the format that represents web pages. That is one of the extensions of XML. Both look the same, the main difference is that HTML has special tags for certain elements, while XML can use any possible tag.

How our swift data would look in XML format:

<language>
    <name>Swift</name>
    <designer>Apple Inc</designer>
    <releaseDate dateformat = longStyle>June 2, 2014</releaseDate>
    <typing>
        <element>static</element>
        <element>strong</element>
        <element>inferred</element>
    </typing>
    <OS>
        <element>Mac OS</element>
        <element>Linux</element>
        <element>FreeBSD</element>
    </OS>
    <license>Apache License 2.0</license>
    <website>swift.org</website>
</language>

We had to use 397 characters to represent the data in XML format. We even have a date format encoded in the data, so one would have an easy time parsing it out.

A thing to note about XML is that opening and closing tags shouldn’t be mixed. For example: <name>Swift<developer>Apple</name></developer>. Swift provides the NSXMLParser class that can be used to decode or encode XML.

In case you find this very similar to a .plist file, you should know that Apple’s property lists are another extension of XML.

JSON

JSON stands for Javascript Object Notation. It's a lightweight data-interchange format. It is easy for humans to read and write. It is easy for machines to parse and generate. It is based on a subset of the JavaScript Programming Language:

{
    "name": "Swift",
    "developer": "Apple Inc.",
    "releaseDate": {
        "format": "longStyle",
        "date": "June 2, 2014"
    },
    "typing": [
        "static",
        "strong",
        "inferred"
    ],
    "OS": [
        "Mac OS",
        "Linux",
        "FreeBSD"
    ],
    "license": "Apache License 2.0",
    "website": "swift.org"
}

This is 264 characters long, quite a change from XML’s 397. This is mostly the reason why JSON is nicknamed “low-fat XML,” as much of the redundancy is gone. In today’s information-hungry world, the 133 extra characters would impact the bandwidth needs of the app’s users.

A JSON object can be thought of as a dictionary in the way that it contains keys and values for those keys.

Types that can be stored in JSON are:

  • Strings ("John Appleseed").
  • Numbers ("3.14", but not “NaN”).
  • Noolean (true, false, True, False).
  • null the absence of value [represented by nil in Swift].
  • Arrays (a list of basic types, arrays, or dictionaries, ["red", "blue", "yellow"]) [represented as Array in swift].
  • Dictionaries (a key-value collection of basic types, arrays, or dictionaries).

An example JSON that can be used to represent a person could be:

{
    "name": "Andrei",
    "age": 20,
    "favourite_colors": [
       "red", 
       "blue"
    ],
    "memories": {
        "best": [
          "Riding the bicycle", 
          "Building model boats"
        ],
        "worst": null
    },
    "is_friendly": true
}

Another example with a list of todos:

[
    {
        "task" : "Buy milk",
        "done" : false      
    },
    {
        "task" : "Eat a cookie",
        "done" : true
    }
]

To make a quick check that it’s a valid JSON, an online validator can be used such as JSON lint. Mismatching parentheses or missing quotes will yield malformed JSON errors.

Arrays or dictionaries contained in other arrays or dictionaries are called nested objects.

In Swift, JSON objects are represented by Dictionary or Array objects. To keep the Swift code clean, it’s good to define constants when dealing with any string type keys. It may be a bit of an overhead for small projects, but the amount of avoided spelling errors and clarity of the code is well worth it.

Working With JSON in Swift

Use Foundation Framework’s JSONSerialization to convert Data types to JSON. It is called serialization because it implies converting an object to a stream of bytes. We get this amazing chance because we chose a standard for transmitting data. Using JSON, we can be fairly certain that any other programming language already has libraries that help parse it.

Creating JSON Objects

Usually, mobile developers don’t need to instantiate JSON data because most of the time it’s provided from server APIs. API stands for Application Programming Interface. In the context of JSON, it refers to a set of requests that can be made to servers and their returned values. In the context of the iOS SDK, APIs mean the same thing but it all happens on the same device. Creating a JSON object is possible by transforming a native Swift dictionary:

import Foundation

let jsonDict = [
    "name" : "Swift",
    "developer" : "Apple Inc.",
    "releaseDate" : [
            "format":"longStyle",
            "date": "June 2, 2014"
    ],
    "typing": ["static", "strong", "inferred"],
    "OS": ["Mac OS", "Linux", "FreeBSD"],
    "license": "Apache License 2.0",
    "website": "swift.org"
] as [String : Any]

if JSONSerialization.isValidJSONObject(jsonDict) {
    if let data = try? JSONSerialization.data(withJSONObject: jsonDict, options: []) {

        print("JSON data object is:\(data)")

        }
    }
}

JSONSerialization.WritingOptions:

  • prettyPrinted specifies that the JSON data should be generated with whitespace designed to make the output more readable. If this option is not set, the most compact possible JSON representation is generated

One interesting observation would be that we can’t add nil in a dictionary. As such, we are left with two options to represent the absence of value:

  • Remove the key.
  • Use NSNull() instead of nil, and have that translated in the JSON null as the serialization occurs.

Reading JSON Objects

Network requests or bundled files return Swift data objects by default. Accessing the contained JSON is done by converting the data object to a Swift dictionary object:

let json = try? JSONSerialization.jsonObject(with: data, options: [])
if let dictionary = json as? [String: Any],
    let name = dictionary["name"],
    let developer = dictionary["developer"],
    let typing = dictionary["typing"],
    let website = dictionary["website"] {

    // treat it as a string key dictionary.
        print("language name: \(name)")
        print("language developer: \(developer)")
        print("language typing: \(typing)")
        print("language website: \(website)")
        ...  
    }

JSONSerialization can return either of the two top-level objects: Array and Dictionary:

if let rootArray = json as? [Any] {
    // treat it as an array.
}

if let rootDictionary = json as? [String: Any] {
    // treat it as a string key dictionary.
}

To try this out, combine the creating and reading code to make the round trip from dictionary to JSON data and then back to a dictionary. Or see the full code below:

Full Round-Trip Code

All contained objects are String, Number, Array, Dictionary or NSNull. Numbers can’t be “NaN” (not a number) or infinity.

JSONSerialization.ReadingOptions:

  • mutableContainers specifies that arrays and dictionaries are created as variables objects, not constants.
  • mutableLeaves specifies that leaf strings in the JSON object graph are created as instances of variable String.
  • allowFragments specifies that the parser should allow top-level objects that are not an instance of Array or Dictionary.

Date Formats Handling With JSON

Date formats

Date formats are usually specified in the server API documentation. Different servers may use different date formats, for example, a U.S.-based server may use the month-day-year notation while European servers use day-month-year notation. Developers need to take into consideration that users can be active in different time zones so moments in time must be represented in a valid way for all of them.

The most commonly used formats on the internet are:

  • epoch time: The number of seconds passed since 1 January 1970.
  • rfc 8601: A standard for date strings that contain the year, month, day, time and time zone.

The most important point to be made here is that the transmitted date should mean the same thing on the server and the client: 1.2.2017, meaning 2 January 2017 won’t end up being represented as 1 February 2017.

One may use a Swift extension to instantiate dates from strings:

extension Date {

    // expects a dictionary containing:
    //    "format": "longStyle",
    //    "date": "June 2, 2014"
    static func date(dictionary: [String: Any]?) -> Date? {
        if let format = dictionary?["format"] as? String,
            let date = dictionary?["date"] as? String {

            if format == "longStyle" {
                let formatter = DateFormatter()
                formatter.dateStyle = .long

                return formatter.date(from: date)
            }
        }
        return nil
    }

    //expects a string like: 2016-12-05
    static func date(yyyyMMdd: String?) -> Date? {
        if let string = yyyyMMdd {
            let formatter = DateFormatter()
            formatter.dateFormat = "yyyy-MM-dd"

            return formatter.date(from: string)
        }
        return nil
    }

    // The date format specified in the RFC 8601 standard
    // is common place on internet communication
    // expects a string as: 2017-01-17T20:15:00+0100
    static func date(rfc8601Date: String?) -> Date? {
        if let string = rfc8601Date {
            let formatter = DateFormatter()
            formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"

            return formatter.date(from: string)
        }
        return nil
    }
}

We’ll the leave epoch time implementation out as an exercise for the reader.

Instantiating Custom Objects From JSON

While having JSON data in dictionaries is a good thing, we would love to be able to add methods and custom behavior to specific objects. The preferred way is to use Swift structs. This is mainly because they are passed by value and we can develop better reasoning about the information they represent over the lifetime of the app.

A simple struct to represent the programming language data structure may be similar to:

struct ProgrammingLanguage {

    let name: String
    let developer: String?
    let releaseDate: Date
    let typing: [String]?
    let os: [String]?
    let license: String
    let websiteURL: URL?

    init?(json: [String: Any]) {

        // As a rule, the developer needs to plan for cases when the data may be polluted.
        // Almost anything can be received, keys may change over updates, contents can change over updates.

        // Use optional chaining for required values
        guard
            let name = json["name"] as? String,
            let releaseDate = Date.date(dictionary:(json["releaseDate"] as? [String: Any])),
            let license = json["license"] as? String
                else {
            // If the JSON is missing any of the required fields, the resulting
            // object wouldn't be considered valid.
            return nil
        }

        self.name = name
        self.releaseDate = releaseDate
            self.license = license

            // Use optionals for non critical values
        self.developer = json["developer"] as? String
        self.typing = json["typing"] as? [String]
        self.os = json["OS"] as? [String]

        self.websiteURL = URL(string:json["website"] as? String ?? "")
    }
}

Consuming a JSON API

JSON is a standard format used for internet communication that is based on the witty structuring of strings to populate apps with data that is relevant to the user.

They are usually received as Swift Data in networking callbacks and get translated to Swift dictionaries or arrays. The developer then uses custom structs to add rich behavior to the objects. Servers that use JSON APIs can vary from weather, fashion, social media, messaging, news, pretty much everything that can be transmitted over the internet.

For your delight, let’s consider using a real life API in Swift:

List Google Books

Google books

Google provides a free JSON API for books. A request can be made to https://www.googleapis.com/books/v1/volumes which is an endpoint. This endpoint needs a qparameter to perform a query with an URL encoded value. The URL encoded value for Swift programming is %22swift%20programming%22. This replaces spaces and other URL reserved characters such as slashes with percent encodings.

The full request with the parameters can be also tried out in the browser: Swift programming google books search.

You should get a response such as:

{
 "kind": "books#volumes",
 "totalItems": 46,
 "items": [
  {
   "kind": "books#volume",
   "id": "Wt66BQAAQBAJ",
   "etag": "6wKECQYXBz8",
   "selfLink": "https://www.googleapis.com/books/v1/volumes/Wt66BQAAQBAJ",
   "volumeInfo": {
    "title": "Beginning Swift Programming",
    "authors": [
     "Wei-Meng Lee"
    ],
    "publisher": "John Wiley & Sons",
    "publishedDate": "2014-12-04",
    "description": "Enter the Swift future of iOS and OS X programming Beginning Swift Programming is your ideal starting point for creating Mac, iPhone, and iPad apps using Apple's new Swift programming language. Written by an experienced Apple developer and trainer, this comprehensive guide explains everything you need to know to jumpstart the creation of your app idea. Coverage includes data types, strings and characters, operators and functions, arrays and dictionaries, control flow, and looping, with expert guidance on classes, objects, class inheritance, closures, protocols, and generics. This succinct — yet complete — overview provides a detailed introduction to the core features of Swift. Apple developed Swift to address the limitations of Objective-C, and add features found in more"
    ...

Making a request with no parameters returns an API error: Google books search without a query.

{
 "error": {
  "errors": [
   {
    "domain": "global",
    "reason": "required",
    "message": "Required parameter: q",
    "locationType": "parameter",
    "location": "q"
   }
  ],
  "code": 400,
  "message": "Required parameter: q"
 }
}

It is the duty of the developer to handle cases when errors are returned from APIs and make sure that his app continues to function correctly. In the case of this particular error, the app user should be notified that he entered no search string and be allowed to make a new search.

Feel free to check out the Google Books API. All APIs are different in the way that they return other data and require different parameters, but most of them have a specification document like this one.

Instantiating Google Books JSON in Swift

After reading the API documentation, we can inspect the returned JSON and select pertinent information to use in the code:

{
 "kind": "books#volumes",
 "totalItems": 46,
 "items": [
  {
...
   "volumeInfo": {
    "title": "Beginning Swift Programming",
    "authors": [
     "Wei-Meng Lee"
    ],
    "publishedDate": "2014-12-04",
    "description": "Enter the Swift future of iOS and OS X programming Beginning Swift Programming ...",*

...
    "imageLinks": {
     "thumbnail": "http://books.google.com/books/content?id=Wt66BQAAQBAJ&printsec=frontcover&img=1&zoom=1&edge=curl&source=gbs_api"
    },
    ...

From this abundance, we can consider a model consisting of a few keys:

struct GoogleBook {

    let title: String
    let authors: [String]
    let publishedDate: Date
    let description: String?
    let thumbnailURL: URL?

    init?(json: [String: Any]) {
    // the initializer should be similar in essence to the ProgrammingLanguage one.
    ...

From the top JSON dictionary, the items key would be extracted. For every item in that array such a GoogleBookshould be instantiated. If all the required keys will be present, of course.

To make an asynchronous request in the Xcode playground we will need to import PlaygroundSupport, enable the needsIndefiniteExecution property and signal the playground that the execution actually stopped by calling PlaygroundPage.current.finishExecution().

The code for actually running the request would look something like the following snippet. Except of course, the forced unwrapping which is a recipe for a crash:

let searchTerm = "%22swift%20programming%22"
let googleBooksEndpoint = "https://www.googleapis.com/books/v1/volumes?q=\(searchTerm)"
let url = URL(string: googleBooksEndpoint)!
let request = URLRequest(url: url)

let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)

let task = session.dataTask(with: request, completionHandler: { (data, response, error) in

    // JSON parsing code.

    PlaygroundPage.current.finishExecution()
})
task.resume()

PlaygroundPage.current.needsIndefiniteExecution = true

A data task is created with the URLSession object, it gets configured with the request and completion handler. On resume() it starts running and will call back on the completionHandler.

Once the request runs, we will need to handle the response information and create all the GoogleBook objects from it:

if let error = error {
        // The error should bhe extracted from it's JSON dictionary and presented to the user.

    } else if let data = data {
        let json = try? JSONSerialization.jsonObject(with: data, options: [])
        if let rootDictionary = json as? [String: Any],
        let items = rootDictionary["items"] as? [[String:Any]] {

            var googleBooks = [GoogleBook]()
            for item in items {
                if let book = GoogleBook(json: item) {
                    print("")
                    print("parsed a book: \(book)")
                    googleBooks.append(book)
                }
            }

            if googleBooks.count == 0 {
                // Should present a message to the user that no results were found.
            }
        }
    }

The error cases are usually handled by displaying an AlertController. It’s important to keep in mind that changes can occur in the received objects at any time (including when the application goes live). Because of this, check and make sure that no forced unwrapping is used and always be able to determine what happens if certain data is invalid.

Feel free to inspect the full code:

Full GoogleBook JSON Code

SwiftyJSON

As one can notice in the above code, there is quite some boilerplate code which checks if actual values are present and are of the expected types. This can create a sizable overhead when dealing with multiple JSON object schemas.

If there were a way to translate:

let json = try? JSONSerialization.jsonObject(with: data, options: [])
        if let rootArray = json as? [[String: Any]],
        let userDict = rootArray[0] as? [String:Any],
        let userName = userDict["name"] as? String {
        //Now you got your value

into:

let json = JSON(data: dataFromNetworking)
if let userName = json[0]["user"]["name"].string {
  //Now you got your value
}

It would reduce the amount of code tremendously. The fantastic library’s name is SwiftyJSON. Don’t forget to give it a star on GitHub!

References

Analysts agree that a mix of emulators/simulators and real devices are necessary to optimize your mobile app testing - learn more in this white paper, brought to you in partnership with Sauce Labs.

Topics:
mobile ,json ,swift ,xml ,api ,objects

Published at DZone with permission of Andrei Nagy, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}