Though its a bit lengthy article, but trust me its going to be worth it.
Most of the time when an app interact with external API or even sometimes local static data, we actually manipulates the different data types like JSON or plist or sometime even some other formats too.
These tasks often require data to be encoded and decoded to and from an intermediate format while the data is being transferred or consumed.
Most of the times, developers uses JSONSerialization to parse the JSON which actually converts the data to dictionary which you have to again parse through and convert to your application friendly data model to make it efficient and easy to read that data every time you need it. And again if you have to send some data to external source using some API, most probably you need to again convert your data model to dictionary to later it being serialised to JSON. Which was obviously tedious task with lot of manual effort.
In this article we will learn the following:
NSCoding was the existing protocol which can enable your data model to and from NSData to manipulate it any further, whether to save in NSUserDefaults, other sources or archiving/unarchiving custom objects.
Making your class complaint to NSCoding protocol require you class to implement two additional method encode(with aCoder:) and init(coder aDecoder:) which again require you to manually serialised and deserialised the properties in each of this method which you require to be converted in to encoded/decoded data.
So, here is the rescue, Swift Standard library now includes new Protocols:
In a nutshell, Encoding is the process of transforming your own custom type, class or struct to external data representation type like JSON or plist or something else & Decoding is the process of transforming external data representation type like JSON or plist to your own custom type, class or struct
typealias Codable = Encodable & Decodable
Adopting Codable on your own types enables you to serialize them to and from any of the built-in data formats, and any formats provided by custom encoders and decoders.
So What types I can Encode and Decode?
If you want to encode or decode any model or your custom type, you must conform it to Codable. Few built in types like String, Int, Double, Date and Data are already conform to Codable.
Built-in types such as Array, Dictionary, and Optional also conform to Codable whenever they contain Codable types.
In below example, all properties can be encoded or decoded as they are either standard Codable types or contains the Codable types.
Lets go in more details by encoding and decoding the data for real scenarios:
There are two encoders available to encode your data to required format:
So, lets start with encoding and decoding some real data.
Consider a few data structures like below:
Encode: lets create a Car instance, and encode it to JSON
Decode: Consider the below JSON, conforming to the Car class metadata. Simply use JSON decoder to decode this JSON back to Car object.
Its very unlikely that you will need to all of the class/struct properties to be encoded or to decode all the fields of JSON to your native model. Like and API response may include a bunch or values in JSON but in you application you may need only few of them. In such case you probably just need to worry about the fields you are interested in rather than decoding or parsing the entire JSON.
Codable types can declare a special nested enumeration named CodingKeys that conforms to the CodingKey protocol. When this enumeration is present, its cases serve as the authoritative list of properties that must be included when instances of a codable type are encoded or decoded. The names of the enumeration cases should match the names you’ve given to the corresponding properties in your type. Omit properties from the CodingKeys enumeration if they won’t be present when decoding instances, or if certain properties shouldn’t be included in an encoded representation.
CodingKey Enum — While using CodingKey enum, it has few rule which needs to be followed while using. CodingKey has RawType as String and Enum values must match case with the JSON key names.
Example: Consider the below defenitions
Encode: Lets encode the object of Person Class.
Decode: Consider the below JSON, conforming to the Person class metadata. Decoded object from JSON will have only name, age, gender properties populated but ‘phone’ and ‘country’ will be nil as we excluded these properties for Encoding/Decoding in CodingKeys enum.
As might be aware till now that declaring CodingKey require the enum values to exactly case math with the property name declared in your class or struct.
But in real world, data received from external API in JSON format may have completely different key names which may or may not match the property names defined in your class. For example, in some API response for list of books, book name key might be ‘bookName’ but that does not mean that you are also forced to use the same name for the property in you model. What if you just wanted to use ‘name’ and this ‘name’ should automatically map to ‘bookName’ key defined in the JSON.
Well, you can achieve this with little bit of code, lets see it how, consider the below class declarations:
Encoding: Lets create a Book object and encode
This encode json will rename the property names in JSON as per above defined CodingKey enum.
name property will get renamed to bookName
author property will get renamed to writtenBy
numberOfPages property will get renamed to pagesInTheBook
All other properties will have the same name in JSON as they are defined in the Book class.
Decoding:Consider the below JSON, conforming to the Book class metadata. Decoded object from JSON will automatically map JSON keys to respective properties in Book class.
5. Encoding/Decoding Nested JSON data
Although, I have already covered it in first example, but lets go through again about how to handle multiple level of Nested objects in JSON data or even in your own class/struct. Lets start with below class declaration inspired by one of the example illustrated on Apple documentation:
Any custom type or collection which contains Codable data type, can be encoded and decoded. In above example each of our nested class is Codable, so do Supermarket.
Encode:Lets create one Supermarket and encode it.
Self explanatory! just check encodedObjectJsonString and it will have all the level of nested information.
Decoding: Consider the JSON created in above example for encodedObjectJsonString variable, To save some space, I will not put this JSON again, we will just reuse the same variable and will try to decode same JSON back to Supermarket object. (Why not you give it a try by creating a JSON by adding multiple products/shelfs/aisles?)
Works like a charm !
All these example are perfectly great until you say that you don’t want to retain the same level of Nesting in your object model as its in JSON or you don’t want same level of Nesting in your JSON as you have defined in your class. Lets target one by one:
6.A. Removing Object Level Nesting while Encoding to JSON
Consider below class declaration conforming to some JSON structure:
When you encode it to JSON, you will get something like:
But actually you want to to be something like:
Lets update our Photo class as below:
First, Include a CodingKey enum in Photo class by specifying the enum values in desired hierarchy:
Notice that CodingKeys enum includes height and width enum values rather than Size as we were following till now.
Now, You need to implement encode(to:) and init(from:) methods of Encodable and Decodable protocols explicitly in your class:
And that it, Once you now encode your object model or decode the JSON data back to your class model by mapping height and width keys to your Size struct in your object model.
6.B. Removing JSON Level Nesting while Decoding to Object model
Remember the Supermarket class and its other related structures which we have used in point 5?
Well, What if we could directly map the Products from JSON under Supermarket class so that my API still uses same JSON format even though my application trims it down to only the required hierarchy and data? To be more clear, I want my Supermarket class to look like this while decoding it from the same JSON.
So my actual JSON from Supermarket class looks like:
But I want my Decode Supermarket class object look like below by avoiding Supermarket->Aisle->Shelf->Product hierarchy:
Unfortunately, Its not directly supported yet in swift. But that does not mean that you can achieve it. So what you can do is keep the original Supermarket class as it is and let it decode all the data with original hierarchy and Create a new class SupermarketData which can match the declaration as mentioned above in ExpectedSupermarket class and write the getter method for allProducts property which can consume the original Supermarket class and can return all the Products across Aisles and Shelves by iterating the collection.
I know its not smart solution but that how you can achieve it until Apple provide inbuilt support for it.
For sample application for all the scenarios used in this article, you can use the code from here.
If you have further comments or questions, feel free to put the below.
Create your free account to unlock your custom reading experience.