Generics that can implement both Void and Codable

Viewed 279

Here I have a struct that I use for api results

struct Response<Result> {
    let status: Int
    let message: String
    let result: Result
}

Now normally getting this to conform to Codable would mean that the Result object needs to be Codable. Doing that would look like one of the following

struct Response<Result: Codable>: Codable {...}

// or

extension Response: Codable where Result: Codable {}

The problem I'm getting is that some responses don't have the result key and I want to be able to use the Response object like with the Void type instead Response<Void> much like this so question.

Currently I have a possible way around this, to just declare another Response type with no result variable inside it like this:

struct BaseResponse {
    let status: Int
    let message: String
}

Is there a way around this so that I don't have to declare another Response type?


I've tried doing the following but nothing works

  1. I can't conform Void to Codable
  2. Have another extension conformance to codable where Result: Void
extension Response: Codable where Result: Codable {}

extension Response: Codable where Result: Void {}
  1. Never also won't work because it doesn't have it's own initializer therefore I can't conform it to Codable
  2. Create a Nothing type that conforms to Codable like this
struct Nothing: Codable, Hashable {
    init() {}

    static var nothing: Nothing { return .init() }
}

So I can use the response like this

let response: Response<Nothing> = Response(
    status: 200,
    message: "Success",
    result: .nothing
)

or

public init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    self.status = try container.decode(forKey: .status)
    self.message = try container.decode(forKey: .message)

    // These lines don't work.
    if let result = try container.decodeIfPresent(Result.self, forKey: .result) {
        self.result = result
    } else {
        self = .nothing
    }
}

But the thing is I can't have a decodeIfPresent method specific for the Nothing type. So much for that.

2 Answers

Make result as optional type Result?. Now, when the response contains the value against result key, it will be set in the result property. Otherwise, it will remain nil if no result key is present.

struct Response<Result: Codable>: Codable {
    let status: Int
    let message: String
    let result: Result? //here....
}

Parse the data like,

do {
    let response = try JSONDecoder().decode(Response<String>.self, from: data)
    print(response)
} catch {
    print(error)
}

Use whatever type you want to in Response<String> as per your requirement.

Example:

  1. If JSON is {"status":1,"message":"This is a message","result":"QWERTY"}

then response is, Response<String>(status: 1, message: "This is a message", result: Optional("QWERTY"))

  1. If JSON is {"status":1,"message":"This is a message"}

then response is, Response<String>(status: 1, message: "This is a message", result: nil))

You could define your init(from:) method like this:

init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)

    status = try container.decode(Int.self, forKey: .status)
    message = try container.decode(String.self, forKey: .message)

    if Result.self is Nothing.Type {
        result = Nothing() as! Result
    } else {
        result = try container.decode(Result.self, forKey: .result)
    }
}

You detect that you're in the Response<Nothing> case and skip the decoding of the result altogether. This way you keep your normal result decoding in the case where a result is required and can leave it non-optional.

Related