# Decoding indeterminate types using JSON Decoder

I'm working with a REST service that responds with indeterminate types. In particular, it can respond with a String, a correct payload (T: Decodable) or an error payload (PayloadError).

In order to cope with this type of requirement I've ended up with the following solution:

do {
let decoder = JSONDecoder()
let either = try decoder.decode(Either<T, PayloadError>.self, from: data)
switch either {
case .left(let value):
completion(.success(value))
completion(.failure(.generic)))
}
} catch {
if let value = String(data: data, encoding: .utf8) as? T {
completion(.success(value))
} else {
completion(.failure(.generic))
}
}


where Either is defined as:

enum Either<T, U> {
case left(T)
case right(U)
}

extension Either: Decodable where T: Decodable, U: Decodable {
init(from decoder: Decoder) throws {
if let value = try? T(from: decoder) {
self = .left(value)
} else if let value = try? U(from: decoder) {
self = .right(value)
} else {
let context = DecodingError.Context(
codingPath: decoder.codingPath,
debugDescription: "Cannot decode \(T.self) or \(U.self)"
)
throw DecodingError.dataCorrupted(context)
}
}
}


Do you think the code looks good or should I use a more elegant solution?

Update 01.11.2020

These are the possible use cases along side with concrete responses.

Use Case 1

The client calls API method /authenticate. The server can responds with an authentication token of type String or a payload error of type PayloadError (if the server responds with unauthorized, for example). The JSON used to decode PayloadError is something like:

{
code: 1,
message: "Error message here"
}


PayloadError is defined as:

struct PayloadError: Decodable {
let code: Int
let message: String
}


Use Case 2

The client calls any other method. For example, it can call /user that gives back the info for the current user (the auth token is passed along). A struct User: Decodable is decoded from the returned JSON. If there is an error, the server replies with a PayloadError.

Based on the above use cases, the server can respond with:

• String
• T: Decodable (like User)
• PayloadError

What I don't like is the code in catch since its intent is not clear.

• Can you provide an example of concrete responses of the various types, and the corresponding T and PayloadError types? – I wonder how String(data: data, encoding: .utf8) as? T works if T is anything but String. Oct 31, 2020 at 19:30
• @MartinR thanks for your comment. I've added an update as you requested. Hope it's clear. Nov 1, 2020 at 10:41
• I assume that .generic is from your custom error type? Perhaps you can add the signature of the completion function (including the used types), just to avoid misunderstandings. – As far as I can see, there is a general problem: If a string is expected, then any response which is not a well-formed PayloadError will be interpreted as a string. Nov 3, 2020 at 11:12
• @MartinR sorry for the delay. I've finally end up splitting the two use cases. See below. Thanks. Nov 5, 2020 at 11:59

I've finally end up splitting the two use cases. This in order to make the code readable and more maintainable.

PayloadError is mapped to a ServiceError: Error in order to align the output.

Use case 1

guard let data = response.data else {
completion(.failure(.invalidData))
return
}

let jsonDecoder = JSONDecoder()
} else if let token = String(data: data, encoding: .utf8) {
completion(.success(token))
} else {
completion(.failure(.decodingError))
}


Use case 2

guard let data = response.data else {
completion(.failure(.invalidData))
return
}
let decoder = JSONDecoder()
guard let either = try? decoder.decode(Either<T, PayloadError>.self, from: data) else {
completion(.failure(.decodingError))
return
}
switch either {
case .left(let value):
completion(.success(value))