Warm tip: This article is reproduced from stackoverflow.com, please click
swift

Generics that can implement both Void and Codable

发布于 2020-03-27 10:30:55

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.

Questioner
Zonily Jame
Viewed
123
dan 2019-07-03 23:43

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.