Codementor Events

Codable in Swift 4.0

Published Jan 12, 2018Last updated Jul 10, 2018
Codable in Swift 4.0

can it replace JSON encode/decode lib out there?

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 cruciel 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 posted on My Medium

Discover and read more posts from Sarun Wongpatcharapakorn
get started