3

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.

Zonily Jame
  • 5,053
  • 3
  • 30
  • 56

2 Answers2

1

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))

PGDev
  • 23,751
  • 6
  • 34
  • 88
  • Yeah I get that, this was my initial implementation actually, but doing this would mean that I'd have to do null checks on the `result` which I don't want to do which is why I created another ResponseType with no `result` inside it. – Zonily Jame Jul 03 '19 at 10:21
  • But in .nothing also, you need to check for result type. In any case, when using the result, you need to check if there is any value in result or not. – PGDev Jul 03 '19 at 10:28
1

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.

dan
  • 9,695
  • 1
  • 42
  • 40
  • I thought this would result to a crash, but it worked. I tried doing this using extensions `extension Response where Result: Nothing` and `extension Response` but it didn't work. – Zonily Jame Jul 04 '19 at 01:25