How to parse JSON in Swift 5 (with Codable)

Parsing JSON it's a task that you will need to implement almost in every project that interacts with a REST API .

For this example, we are going to use Twitter API to get statuses class.

{
"created_at": "Wed Oct 10 20:19:24 +0000 2018",
"id": 1050118621198921728,
"id_str": "1050118621198921728",
"text": "To make room for more expression, we will now count all emojis as equal—including those with gender‍‍‍ and skin t… https://t.co/MkGjXf9aXm",
"lang": "en",
"user": {
		"id": 6253282,
		"description": "The Real Twitter API. Tweets about API changes, service issues and our Developer Platform. Don't get an answer? It's on my website.",
		"url": "https://t.co/8IkCzCDr19",
		"name":"Twitter API"
	}
}
A minimized version of Twitter API GET statuses/home_timeline

JSONSerialisation

We have the native option which is to use the JSONSerialisation class method jsonObject(with:options:) which returns a value of type Any. Then you will most probably cast it in [String:Any] in order to access the keys and after getting the value of a specific key you should cast it again.

if let statusesArray = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [[String: Any]],
    let user = statusesArray[0]["user"] as? [String: Any],
    let username = user["name"] as? String {
    // Finally we got the username
}
Accessing the key 'name' using JSONSerialisation

SwiftyJSON

We also have open source solutions like SwiftyJSON which is simplifying the process by removing a lot of boilerplate code.  

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

What I don't like about the JSONSerialisation and SwiftyJSON is that you need to access the values using a plain "string" which is prone to error. You can always use Enum to fix that but it's an extra step in the procedure.

Encodable, Decodable and Codable

In Swift 4, Apple introduced Encodable and Decodable protocols that standardize the encoding/decoding procedure. All you need to do is to declare variables that match the "key" in JSON and they have one of the following types: Int, String, Date, Data, URL and Double.

You can also encode/decode an Array and a Dictionary if they contain objects that conform to Codable.

Codable is a protocol that includes Encodable and Decodable at the same time.

Let's see how our Status and User models will look like if we are parsing the statuses from Twitter API.

struct Status : Codable {
	var id : Int
    	var id_str : String
	var text : String
	var user: User
	var lang: String
}
struct User : Codable {
	var id : Int
	var description : String
	var url : URL
	var name : String
}

And decoding and accessing the name of the User will be simple as that:

let jsonData = Data() //downloaded from API
if let status = try? JSONDecoder().decode(Status.self,from: jsonData) {
    print(status.user.name)
}

Look how clean it is!!

What we did here is that we created a Status struct conforming to Codable in order to match our JSON structure. This is enough if all of the values are String or Int. But we have a User key also that represents a new model, so we created the User struct that is also conforming to Codable hence we can use it in the parent struct.

When creating a model that conforms to Codable in order to use it for JSON decoding we need to make sure that our variable names match the "key" names of the JSON structure.

For Status model we used id_str in order to match the "key" name in JSON.  Some of you are already cringing about the idea using under_score in your model's variable. Don't worry because there is a way to keep the name convention consistent.

struct Status : Codable {
    var id : Int
    var idStr : String
    var text : String
    var user: User
    var lang: String
    
    enum CodingKeys: String, CodingKey {
        case id
        case idStr = "id_str"
        case text
        case user
        case lang
    }
}

This way we mapped our model's variable idStr with the JSON's key id_str.

Things to have in mind about Codable

1) If we declare a variable in our Model and this variable is not visible in our JSON as a key then the JSONDecoder will return nil. So if we are not sure about a variable we are declaring it as an Optional so the JSONDecoder will return an instance of the model.

2) It can get tricky if you have complex(nested)  JSON structures. You will end up creating models in order to access one level and then more models to go deeper. I would suggest using an alternative for these cases.

Happy coding!