Recently I got this interesting problem to solve. To parse a JSON with the content of dynamic type. JSON looked something like this:-
As one can see a value for property **content *can be an *Int or String. *In future, there can be different types of content added like **Float, Dictionary etc.*
In Swift, we use Decodable to parse any JSON to its respective Model. So let’s first create an equivalent Struct for above-mentioned JSON. On my first try, I created something like this:-
So what will be the type of content mentioned in Struct Body?
On my first thought, I would have made the **content **of type T where T: Decodable. This will work if **struct Body **is not used in any type of collection. But that is not the case so this approach of keeping generic type won’t work. After searching through the internet and learned how people have solved this problem I came up with these 2 approached. In a way, these 2 approaches are kind of similar but yet different in terms of implementation and usage.
How to solve this?
As mentioned I will try to present 2 solutions and explain them in detail. The solutions are as follows:-
Using enum with associative values
In this first approach, we will use an enum with cases representing the type of content and it’s associated value will hold the actual content type.
In the above code as we can see that the content is represented by an enum value whose cases are matching **type **and the associated values are the actual content. We now create an array of these enums type.
Since now we have defined our model as per the 1st solution proposed let's try to write some code to deserialize a JSON and then use the model.
The above code looks pretty easy but there are few shortcomings of the above solution as mentioned below:-
To fetch a content value from enum is a tedious task of always writing a switch case. This adds to a lot of boilerplate code
When adding a new value will lead to changing at multiple places like need to change the Model file for adding a new type of decoding. Also, need to handle this new type in all the switch cases which will not give compile time error
So this solution works and is pretty good but it comes with its own baggage of boilerplate code and difficult to add new types. I believe we can add some syntactic sugar to simplify the consumption of the associative value. I will leave that discussion for some other day.
Using Generic & Protocol
Let’s get into the 2nd way to solve this problem. In this solution, we will use Generics and Protocols to create a wrapper for the dynamic content of the JSON. Here is the code:-
In this approach, we need to create a protocol called **BodyContent **which is used to get the value of the content. With this, we create **struct ContentData **which holds the content value of type T and implements protocol BodyContent to provide a way to get that value. Now we define the **content of type BodyContent **protocol which can hold the initialised value of **ContentData. **This gives the ability to hold dynamic content. Let’s see now how we can use this implementation.
The usage of this solution also looks pretty easy and even it has lesser boilerplate than out previous code but it also has its own issues. Let's check those shortcomings:-
When consuming the value using function getContent() we should be completely aware about the type of content that we are accessing. As getContent() function is a generic function and compiler needs to know it's type at compile type.
When adding a new type will require to add additional if conditions in the model to decode those content. This affects the model but unlike our previous solution we don’t need to change everywhere, it’s been used. As there the user strictly knows the type of the content and it doesn’t care about other types
Conclusion
We have gone through above mentioned 2 solutions for decoding dynamic types. Each one has its own advantages and disadvantages and needs to be used as per the requirements. I have also found this other solution quite interesting which creates a JSONValue type as a global custom type and try to decode the values in one of the mentioned cases. Check here: link. This implementation though looks similar to our 1st solution using enum but the writer has tried to generalize the implementation.
Both the above-mentioned solution works well and can scale better with the addition of some syntactic sugar and better support of generics by Swift language. That can help to remove the boilerplate code.