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. 😉
may this is an another method https://github.com/stardustWK/JSONDecoderExtesions/blob/master/JSONDecoderExtesions.swift
Pingback: How to use Any in Codable Type - iZZiSwift