Decoding Dynamic JSON with Swift Codable
Handle dynamic server responses working with Swift’s sound type system
Providing dynamic content in your app requires communicating with an external service to fetch data. The most common format that is used nowadays is JSON and the common way of handling JSON data in swift is to convert it to swift types with help of Codable. In the majority of the use cases, the data can be represented with a concrete swift type having a static list of properties.
In other cases, when the content in your app is highly dynamic with a wide range of scope, the response data may vary between a wide range of formats. Some common examples are:
- Building dynamic server-driven UI with CMS.
- Display a wide range of products on an e-commerce website.
- Dynamic content feed in a social network.
In this post, we will see the typical way of handling decoding these kinds of responses, and how DynamicCodableKit makes it seamless to handle dynamic responses by building on top of Swift’s Codable.
Current State
Following is Amazon’s suggestion response data when a user types any text in the search box, in this scenario “microwave”:
The suggestions
property contains a list of dynamic results for the search, with each result containing a field type
representing the actual type of result. In the above response, KEYWORD
and WIDGET
type results are received, with each type containing some additional metadata, which in turn is used to present the additional option to users like this:
One approach is to use a single struct
with optional fields. Although this approach seems simpler, with this approach you can use default synthesized Codable
implementation. The drawback of this approach is you have to pollute your business logic with data validation code and for cases where the same property can have a different type you are out of luck with this approach.
Another and much-preferred approach is to use an enum
with associated values. It addresses the shortcoming of the previous approach of removing nil
property requirements and solving property type conflicts by adding separate cases for each type to decode. Custom init(from:)
implementation for the enum
can be provided that will decode the underlying case/type.
In our example, we can check the type
field for each object and decode the suggestion accordingly:
Using DynamicCodableKit
While the above approach works for responses with a smaller level of dynamism, it becomes much harder to maintain all this boilerplate. The major drawback is you have to do some additional work to access common properties across all cases and with enums, you have to update all the switch cases each time you add a new type.
This is where DynamicCodableKit shines by using generic property wrappers instead. It handles dynamic decoding based on DynamicDecodingContext
which is a type erasure that decodes a specific DynamicDecodable
type and casts it to its generic type. You can customize DynamicDecodingContext
in different scenarios to dynamically decode multiple types. DynamicCodableKit also provides multiple configurations for collection decoding, to customize what happens when invalid data is encountered, i.e. you can choose to decode only valid data while ignoring invalid data instead of just throwing an error.
For our current example, we can create a Suggestion
protocol type that will provide access to common properties and methods for underlying actual Suggestions, i.e. KeywordSuggestion
, WidgetSuggestion
. SuggestionType
and SuggestionTypeCodingKey
can implement DynamicDecodingContextIdentifierKey
and DynamicDecodingContextIdentifierCodingKey
respectively to decode the actual types. Compared to the previous approach, adding new types only requires changing SuggestionType
's associatedContext
implementation:
NOTE: By default,
DynamicDecodable
only supports down casting, if you want to cast to an unrelated type, you have to provide a custom implementation.
Conclusion
Besides the above example, DynamicCodableKit provides several property wrappers and associated protocols to handle various dynamic decoding scenarios, all of them explained in detail in the documentation making dynamic decoding a lot simpler to use with minimal boiler-plate.