Generic JSON Parsing

Mon Mar 26 2018 | Mark Struzinski

I have been working a lot in model layers lately. One benefit of this has been converting our older JSON parsing code over to using Swift 4’s enhanced JSON parsing capabilities. Using the Codable protocol has allowed me to delete hundreds of lines of boilerplate and streamline a lot of repetitive and error-prone code.

A lot of this work is pretty straightforward, where we are mapping properties to types, and sometimes including a CodingKey protocol object to ensure property names are mapped to JSON keys correctly. However, I ran across one interesting scenario and I wanted to share my solution to it.

I’ll present a simple generic object and its associated json in this post, and then show how that expanded out to a pretty nice solution once that smaller object became part of the full service in a future post.

I have a service response that includes a list of values keyed by an app version that have the same bacis structure, but can return different types. We use this file to enable and disable app features, and to control values, such as strings for contact numbers and messaging. Each value entry has the same structure, but it could come typed as a String or Int or any other type.

Here is a small example:

{
    "customer_care_number": {
        "app_version": "1.0.0",
        "value": "888-999-1111" 
    },
    "product_search_limit": {
        "app_version": "1.0.0",
        "value": 6
    } 
}

All entries have this same format. Aside from the key that identifies each object, each entry has an app_version key and a value key. Rather that going through a tedious and long custom decoding process for these, I decided to try to compose them using generics.

Here is what I came up with:

struct PropertyEntry<T: Codable>: Codable {
    let appVersion: String
    let value: T
}

enum CodingKeys: String, CodingKey {
    case appVersion = "app_version"
    case value
}

The main object that holds all of these entries is called BusinessProperties.

Here is what the setup looks like for that file:

struct BusinessProperties: Codable {
    let customerCareNumber: PropertyEntry<String>
    let productSearchLimit: PropertyEntry<Int>

    enum CodingKeys: String, CodingKey {
        case customerCareNumber = "customer_care_number"
        case productSearchLimit = "product_search_limit"
    }
}

This was one of those times that the simple solution didn’t seem like it should work. But once I wrote some unit tests to check it, everything decoded exactly as expected. I was very impressed with the simplicity and expressiveness of this solution. Swift is such a nice language to work in.

In my next post, I’ll take this approach further with some more complex objects.