11 Nov 2018 Post by: Nhat Le
Swift codable help auto-generated json to your object models more easily.
To see how it work, just suppose we have a login api which will response the user info as the json:
{
"userid": 1002,
"user_name": "nhatle",
"email": "lenhathk@gmail.com",
"access_token": "akjfkadT10cm543sfdsf"
}
How to parse the json to your user model?. It’s really easy, first of all you need define your user model like that:
struct UserModel: Codable {
let id: Int
let name: String
let email: String
let token: String
enum CodingKeys: String, CodingKey {
case id = "userid"
case name = "user_name"
case email
case token = "access_token"
}
}
As you see the field email
in CodingKeys enum didn’t have string to represent for it, just because the field name have same string with the key in json string. Now, you parse the json string to your model like that:
do {
let jsonData = json.data(using: .utf8)!
let user = try JSONDecoder().decode(UserModel.self, from: jsonData)
}
catch let error {
print(error.localizedDescription)
}
How about nested json? like that:
{
"userid": 1002,
"user_name": "nhatle",
"email": "lenhathk@gmail.com",
"access_token": "akjfkadT10cm543sfdsf",
"addresses": [
{
"street": "Linh Dong",
"city": "HO CHI MINH"
},
{
"street": "Thanh Thai",
"city": "HO CHI MINH"
}
]
}
Don’t worry, you just need to change a little bit your user model and add Address
model to make your code run properly:
struct UserModel: Codable {
let id: Int
let name: String
let email: String
let token: String
let addresses: [Address]
enum CodingKeys: String, CodingKey {
case id = "userid"
case name = "user_name"
case email
case token = "access_token"
case addresses
}
}
struct Address: Codable {
let street: String
let city: String
}
You done, with out need change any code in the place you parsed the json to your model as above.
All things seem run smoothly until the day server team decided that into the response json of the login api addresses
’s field is optional. It’s mean some users will have addresses field and another will miss it. And the the code you did before not run for some users which don’t have addresses.
You can change the codable model to fixed it by adding required init(from decoder: Decoder) throws
to your user model:
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decodeIfPresent(String.self, forKey: .name) ?? "Your default value"
...
self.addresses = try container.decodeIfPresent(Array<Address>.self, forKey: .addresses) ?? []
}
Done! But it’s still ridiculous that auto-generated methods cannot read the default values from optionals.