Swift 4 Codable in Real life, Part 2

      2 Comments on Swift 4 Codable in Real life, Part 2

In this article we will try to cover another possible scenario that can happen while working on APIs poorly maintained: "the unexpected  type problem".In the first part of the fantastic world of Codable we tackled just one of the possible problems that in real life could make this amazing language feature hard to use. In this article we will try to cover another possible scenario that can happen while working on APIs poorly maintained: “the unexpected  type problem”.

First of all I would like to say that if it is an option the best way to go to solve any possible problem on client side for unexpected behaviours of the backend, would be probably to talk with your backenders and try to find a solution to the problem fixing it on their side. The best solution will always be the one that does not need workarounds. While this is recommended, sometimes to fix the issue at the origin is not an option for many possible reasons, so here we are to invent workarounds to try to use Codable in Real life.

The Problem

The problem I would like to talk about today is when for a particular field of your json you can get two different values. This is a common mistake I saw many times especially when the field is expected to be an integer or a boolean and instead you get a string.

[{
    "name": "Gala",
    "age": 1,
    "type": "Pug"
}, {
    "name": "Keira",
    "age": "7",
    "type": "Collie Rough"
}]

In this json for example we have the field age that can either be a string or an Int. In my experience is very common also the case where field expected to be a boolean was actually serialised in json as “true”/”false” or 1/0.

The Solution

The solution I found to tackle this issue is to introduce a new generic Decodable Type that I called UncertainValue. With the use of generic we will be able to create this UncertainValue to be able to decode a single field using a primary type, or fallback in a secondary type if the first attempt was not successful.

public struct UncertainValue<T: Decodable, U: Decodable>: Decodable {
    public var tValue: T?
    public var uValue: U?
    
    public var value: Any? {
        return tValue ?? uValue
    }
    
    public init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        tValue = try? container.decode(T.self)
        uValue = try? container.decode(U.self)
        if tValue == nil && uValue == nil {
            //Type mismatch
            throw DecodingError.typeMismatch(type(of: self), DecodingError.Context(codingPath: [], debugDescription: "The value is not of type \(T.self) and not even \(U.self)"))
        }

    }
}

The only requirement of T and U is just that the type is Decodable, therefore it is possible to chain UncertainValue types to have more than one fallback. For example UncertainValue<Bool, UncertainValue<Int, String>> and as usual in your Decodable object it can be declared optional.

Another situation where this UncertainValue struct might come handy is the case where a field can either contain an array or a single value. Imagine that your json is

{
    "results": [{
            "name": "Gala",
            "age": 1,
            "type": "Pug"
        }, {
            "name": "Keira",
            "age": "7",
            "type": "Collie Rough"
        }]
}

In case of multiple results, or

{
    "results": {
            "name": "Gala",
            "age": 1,
            "type": "Pug"
        }
}

When there is just one result. In this case the type of results would be UncertainValue<[Dog], Dog> This is clearly another case where the backend is to blame.

So, now we are ready to create our dog struct

struct Dog: Decodable, CustomStringConvertible {
    var name: String
    var age: UncertainValue<Int, String>
    var type: String
    
    var description: String {
        return "\(name) is a lovely \(type) of \(age.value!) years old"
    }
}

Defining age to be an UncertainValue between Int and String is all we have to do, and we are ready to decode our json.

let dogsDecoder = JSONDecoder()
let dogs = try! dogsDecoder.decode([Dog].self, from: dogsJSONData)

Both Dogs will be correctly decoded.

If you have other cases that requires workaround to work with Codable write them in the comments. We will try together to find a solution. 😉

2 thoughts on “Swift 4 Codable in Real life, Part 2

  1. Pingback: How to use Any in Codable Type - iZZiSwift

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.