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.
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.
SwiftyJSON
We also have open source solutions like SwiftyJSON
which is simplifying the process by removing a lot of boilerplate code.
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!