Codable in Swift 4.0

can it replace JSON encode/decode lib out there?

Sarun W.
4 min readJul 9, 2017

--

Head over to sarunw.com for the very best version of this post and more awaits.

After I watched WWDC 2017 and heard about Codable I’m thinking of replacing my current JSON encode/decoder in my projects or at least use it in a new one.

I’m happy to see Apple finally come up with this encoder/decoder built into Swift standard lib, since its such a mandatory tasks nowdays and for me I haven’t seen a clear winner in this area. I try to avoid using third part library as much as possible, so I’m really excited to explore its possibility and limitation.

What I looking for in encode/decode?

Swift optional type supported

The most important thing I need is a Swift optional type supported, this is very crucial for me, without this it is a deal-breaker.

Luckily Codable support this. If you have following User Object.

struct User: Codable {
var firstName: String
var lastName: String
var middleName: String?
}

These JSON strings are valid.

{
"firstName": "John",
"lastName": "Doe",
"middleName": null
}
{
"firstName": "John",
"lastName": "Doe"
}

Be able to rename properties

If you have ever work with REST API you will see that most JSON keys doesn’t use CamelCase naming, but snake_case. Codable also support rename property keys and its very easy to do so.

All you need to do is adding a nested enumeration named CodingKeys that conforms to the CodingKey protocol.

From above example we can rename it like this.

struct User: Codable {
var firstName: String
var lastName: String
var middleName: String?
enum CodingKeys: String, CodingKey {
case firstName = "first_name"
case lastName = "last_name"
case middleName = "middle_name"
}
}

And you will be able to decode this JSON

{
"first_name": "John",
"last_name": "Doe",
"middle_name": null
}
{
"first_name": "John",
"last_name": "Doe"
}

Custom mapping between JSON and Swift structure.

There are cases where we have no control over how JSON look like, be able to have different Swift structure than JSON is nice-to-have feature.

I will test on 2 common cases.

  1. Flatten out JSON nested property.
  2. Make nested structure from flat JSON.

Flatten out JSON

Let say you have User JSON that contain nested billingAddress property.

{
"name": "John",
"billingAddress": {
"district": "District",
"subDistrict": "Sub District",
"country": "Country",
"postalCode": "Postal Code"
}
}

But somehow you want to layout Swift User like this.

struct User: Codable {
var name: String
var district: String
var subDistrict: String
var country: String
var postalCode: String
}

You need to define two enumerations that each list the complete set of coding keys used on a particular level.

struct User: Codable {
....
enum CodingKeys: String, CodingKey {
case name
case billingAddress
}
enum BillingAddressKeys: String, CodingKey {
case district
case subDistrict
case country
case postalCode
}
}

Since this isn’t direct mapping we need to implementing Decodable's required initializer, init(from:):

init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
name = try values.decode(String.self, forKey: .name)
let billingAddress = try values.nestedContainer(keyedBy: BillingAddressKeys.self, forKey: .billingAddress)
district = try billingAddress.decode(String.self, forKey: .district)
subDistrict = try billingAddress.decode(String.self, forKey: .subDistrict)
country = try billingAddress.decode(String.self, forKey: .country)
postalCode = try billingAddress.decode(String.self, forKey: .postalCode)
}

And same apply to Encodable protocol, a custom encode(to:):

func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)
var billingAddress = container.nestedContainer(keyedBy: BillingAddressKeys.self, forKey: .billingAddress)
try billingAddress.encode(district, forKey: .district)
try billingAddress.encode(subDistrict, forKey: .subDistrict)
try billingAddress.encode(country, forKey: .country)
try billingAddress.encode(postalCode, forKey: .postalCode)
}

Nested structure

This is opposite of what we just did, we got JSON like this.

{
"name": "John",
"district": "District",
"subDistrict": "Sub District",
"country": "Country",
"postalCode": "Postal Code"
}

And want this Swift structure

struct User: Codable {
var name: String
var billingAddress: BillingAddress
}
struct BillingAddress: Codable {
var district: String
var subDistrict: String
var country: String
var postalCode: String
}

Following are what we need to implement

struct User: Codable {
....
enum CodingKeys: String, CodingKey {
case name
case billingAddress
case district
case subDistrict
case country
case postalCode
}
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
name = try values.decode(String.self, forKey: .name)
billingAddress = try BillingAddress(from: decoder)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)
try container.encode(billingAddress.district, forKey: .district)
try container.encode(billingAddress.subDistrict, forKey: .subDistrict)
try container.encode(billingAddress.country, forKey: .country)
try container.encode(billingAddress.postalCode, forKey: .postalCode)
}

Conclusion

Codable pass all my criteria, it can do what I needed with easy to understand syntax. The only aspect that I didn’t touch is performance (I think it would be good). My conclusion is I definitely use it for my next project.

Originally published at https://sarunw.com.

Weekly blog at http://sarunw.com

--

--

Sarun W.

iOS Developer at Oozou — Code & design iOS app — Follow me on https://twitter.com/sarunw for everything iOS — Blogging weekly at http://sarunw.com