Win a ticket for the Do iOS Conference in Amsterdam Join here.
Give your simulator superpowers

RocketSim: An Essential Developer Tool
as recommended by Apple

JSON Parsing in Swift explained with code examples

JSON parsing in Swift is a common thing to do. Almost every app decodes JSON to show data in a visualized way. Parsing JSON is definitely one of the basics you should learn as an iOS developer.

Decoding JSON in Swift is quite easy and does not require any external dependencies. The basic APIs that come with Swift will be enough to do the job, so let’s dive in!

The basics of JSON decoding

It’s good to start with the basics to let you understand how JSON parsing in Swift works. Let’s take the following example of a SwiftLee blog post:

{
	"title": "Optionals in Swift explained: 5 things you should know",
	"url": "https://www.avanderlee.com/swift/optionals-in-swift-explained-5-things-you-should-know/",
	"category": "swift",
	"views": 47093
}

We can easily decode this by making use of the Decodable protocol:

struct BlogPost: Decodable {
    enum Category: String, Decodable {
        case swift, combine, debugging, xcode
    }

    let title: String
    let url: URL
    let category: Category
    let views: Int
}

We defined a Category enum that also conforms to the Decodable protocol. All the properties match the names from our defined JSON example. Every type that conforms to the Decodable protocol automatically converts. This means that you can also use your own custom-defined Decodable types as a property.

By making use of a JSONDecoder we can make JSON parsing possible:

let JSON = """
{
    "title": "Optionals in Swift explained: 5 things you should know",
    "url": "https://www.avanderlee.com/swift/optionals-in-swift-explained-5-things-you-should-know/",
    "category": "swift",
    "views": 47093
}
"""

let jsonData = JSON.data(using: .utf8)!
let blogPost: BlogPost = try! JSONDecoder().decode(BlogPost.self, from: jsonData)

print(blogPost.title) // Prints: "Optionals in Swift explained: 5 things you should know"

Although this might give the impression that JSON parsing is easy, it all comes down to the edge cases. Luckily enough, Swift is capable enough to handle those as well.

It’s not required to define each property

It’s good to know that you’re not required to define each property that comes with your JSON. This means that the following struct would’ve worked as well:

struct BlogPost: Decodable {
    let title: String
}

This is great, as it could be that you’re adding new keys to the JSON backend response after you’ve already released a version of your app. If it wouldn’t work like this, you could easily break old versions.

Optionals and JSON decoding

It could be that you’re unsure whether a JSON key is returned or a value will be set. In this case, you can define a Swift property as optional, and the JSONDecoder will take care of the rest.

struct BlogPost: Decodable {
    let title: String
    /// Define a key as optional if it can be returned as `nil` or if it does not always exist in the JSON.
    let subtitle: String?
}

I recommend reading my article Optionals in Swift explained: 5 things you should know if you’re new to optionals.

Stay updated with the latest in Swift

Join 20,033 Swift developers in our exclusive newsletter for the latest insights, tips, and updates. Don't miss out – join today!

You can always unsubscribe, no hard feelings.

Decoding JSON arrays in Swift

Decoding a JSON array in Swift is almost as easy as decoding a single JSON object. Take the following JSON example:

[{
    "title": "Optionals in Swift explained: 5 things you should know",
    "url": "https://www.avanderlee.com/swift/optionals-in-swift-explained-5-things-you-should-know/"
},
{
    "title": "EXC_BAD_ACCESS crash error: Understanding and solving it",
    "url": "https://www.avanderlee.com/swift/exc-bad-access-crash/"
},
{
    "title": "Thread Sanitizer explained: Data Races in Swift",
    "url": "https://www.avanderlee.com/swift/thread-sanitizer-data-races/"
}]

We can parse this list of blog posts by defining the decodable type as [BlogPost].self:

struct BlogPost: Decodable {
    let title: String
    let url: URL
}

let blogPosts: [BlogPost] = try! JSONDecoder().decode([BlogPost].self, from: jsonData)
print(blogPosts.count) // Prints: 3

Mapping JSON keys to custom property names

JSON parsing isn’t always as easy as copying over the same keys into a struct. It’s pretty common that you like to define different property names when mapping the JSON.

Taking the previous JSON example, it could be that we would like to name url as htmlLink in our JSON model. We can create this mapping by defining a custom CodingKeys enum:

struct BlogPost: Decodable {
    enum Category: String, Decodable {
        case swift, combine, debugging, xcode
    }

    enum CodingKeys: String, CodingKey {
        case title, category, views
        // Map the JSON key "url" to the Swift property name "htmlLink"
        case htmlLink = "url"
    }

    let title: String
    let htmlLink: URL
    let category: Category
    let views: Int
}


let blogPost: BlogPost = try! JSONDecoder().decode(BlogPost.self, from: jsonData)
print(blogPost.htmlLink) // Prints: "https://www.avanderlee.com/swift/optionals-in-swift-explained-5-things-you-should-know/"

As you can see, we defined a custom mapping to convert the JSON key url into the Swift property name htmlLink.

As we’re not changing the name of title, category, and views, we can keep this case the same. We do have to include those keys as the JSONDecoder will switch to our defined mapping for all defined properties. If we don’t do it, we will run into the following error:

Type ‘BlogPost’ does not conform to protocol ‘Decodable’

Conversion between camel case and snake case

A common reason to define custom mapping for keys is that your backend uses snake case for naming properties. In Swift, we’re mainly using camel case, which means that we start with a lowercase letter and then capitalize the first letter of subsequent words: htmlLink or numberOfBlogPosts. The same words in snake case look as follows: html_link and number_of_blog_posts.

Luckily, we don’t have to define a custom mapping for each key. Take the following example JSON of a blog:

{
    "title": "A weekly Swift Blog on Xcode and iOS Development - SwiftLee",
    "url": "https://www.avanderlee.com",
    "total_visitors": 378483,
    "number_of_posts": 47093
}

We can easily decode that JSON by setting the keyEncodingStrategy of our decoder to .convertFromSnakeCase:

struct Blog: Decodable {
    let title: String
    let url: URL
    let totalVisitors: Int
    let numberOfPosts: Int
}

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

let blog: Blog = try! decoder.decode(Blog.self, from: jsonData)
print(blog.numberOfPosts) // Prints: 47093

That was super easy! This also works fine with custom-defined keys. So, if you would like to map url to htmlLink just like we did before, you can easily do that as follows:

struct Blog: Decodable {

    enum CodingKeys: String, CodingKey {
        case title, totalVisitors, numberOfPosts

        case htmlLink = "url"
    }

    let title: String
    let htmlLink: URL
    let totalVisitors: Int
    let numberOfPosts: Int
}

Decoding JSON dates with custom formats

Dates in JSON are defined as a String or time interval and require a conversion strategy. We can set such a strategy for our JSONDecoder, just like we did for converting camel case to snake case.

Take the following JSON example of a blog post:

{
    "title": "Optionals in Swift explained: 5 things you should know",
    "date": "2019-10-21T09:15:00Z"
}

The date in this example is defined in the following format: yyyy-MM-dd'T'HH:mm:ss. We need to create a custom DateFormatter with this format and apply this to our decoder by setting the dateDecodingStrategy to formatted:

struct BlogPost: Decodable {
    let title: String
    let date: Date
}

let decoder = JSONDecoder()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
dateFormatter.locale = Locale(identifier: "en_US")
dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
decoder.dateDecodingStrategy = .formatted(dateFormatter)

let blogPost: BlogPost = try! decoder.decode(BlogPost.self, from: jsonData)
print(blogPost.date) // Prints: 2019-10-21 09:15:00 +0000

There are a few other strategies available to set:

  • deferredToDate: Uses Apple’s date format that tracks the number of seconds and milliseconds since January 1st, 2001. This is mainly useful to use directly with Apple’s platforms.
  • millisecondsSince1970: This format tracks the number of seconds and milliseconds since January 1st of 1970 and is much more common.
  • secondsSince1970: Tracks the number of seconds since January 1st of 1970.
  • iso8601: Decodes the Date as an ISO-8601-formatted string (in RFC 3339 format).

Depending on how the API you’re using returns the dates, you can choose between those strategies.

Benefiting from AI to convert JSON into Swift

My number one in my personal top 5 AI code generation prompts is about parsing JSON into a Swift struct. It’s a straightforward conversion that AI can handle very well. The prompt would be like:

Can you write a Swift struct called Person for the following JSON [JSON]

And you’ll get a working code example out of it. Do note that AI often generates more than needed, including a custom coding key in all cases. Therefore, you’ll have to do some manual cleanup, like you always have to check AI coding results.

How to pretty print JSON in Swift

Printing JSON in a pretty format is useful during debugging, for example, when finding out what a backend has to return.

You can pretty print an encodable type using the following extension:

extension Encodable {
    func prettyPrintJSON() {
        let encoder = JSONEncoder()
        encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
        guard let encodedData = try? encoder.encode(self) else {
            print("Failed to encode data")
            return
        }
        
        let prettyJSONString = String(decoding: encodedData, as: UTF8.self)
        print(prettyJSONString)
    }
}

For example, when using it on the Blog struct that we defined earlier:

let blog: Blog = ...
blog.prettyPrintJSON()

// Prints:
// {
//   "numberOfPosts" : 47093,
//   "title" : "A weekly Swift Blog on Xcode and iOS Development - SwiftLee",
//   "totalVisitors" : 378483,
//   "url" : "https:\/\/www.avanderlee.com"
// }

If you want to pretty print a data response from a URL request that you’ve performed, you can use the following extension:

extension Data {
    func prettyPrintJSON() {
        guard let object = try? JSONSerialization.jsonObject(with: self) else {
            print("Could not create JSON object from data")
            return
        }
        
        guard let serializedData = try? JSONSerialization.data(
            withJSONObject: object,
            options: [.prettyPrinted, .sortedKeys]
        ) else {
            print("Could not serialize JSON data")
            return
        }
        
        let prettyJSONString = String(decoding: serializedData, as: UTF8.self)
        print(prettyJSONString)
    }
}

How to read a JSON response from a request in Swift

When actively implementing API requests, you’re likely looking for a way to inspect network traffic and read the returned JSON. By reading the JSON, you can properly convert the JSON into a Swift struct to work with. My recommended way of working is to use the technique described in this article to inspect network traffic in the Simulator: Inspect network traffic using the Xcode Simulator.

Once you have the JSON, you can use the techniques described in this article to convert it into a Swift value type.

Conclusion

Swift makes decoding JSON easy. There’s no need to use a custom library for JSON parsing as the default API brings everything we need, from custom key mapping to formatting dates.

If you like to improve your Swift knowledge, even more, check out the Swift category page. Feel free to contact me or tweet to me on Twitter if you have any additional tips or feedback.

Thanks!

 
Antoine van der Lee

Written by

Antoine van der Lee

iOS Developer since 2010, former Staff iOS Engineer at WeTransfer and currently full-time Indie Developer & Founder at SwiftLee. Writing a new blog post every week related to Swift, iOS and Xcode. Regular speaker and workshop host.

Are you ready to

Turn your side projects into independence?

Learn my proven steps to transform your passion into profit.