0
\$\begingroup\$

I need to integrate some server API calling in which I'm likely to get response as:

+ Response 200 (application/json)
+ Body
        {
          "code": 0,
          "status": "ok",
          "message": "Verification Message from API",
          "result": {
            "values": [
              {
                "verify_status": true,
                "token_key": "sure1ff45jk7858b8ae88a77fa30"
              }
            ]
          }
        }

Sometimes the response will be as:

+ Response 200 (application/json)
+ Body
        {
          "code": 0,
          "status": "ok",
          "message": "Update Message from API",
          "result": {
            "values": [
              {
                "update_status": true
              }
            ]
          }
        }

As you can see, mostly all the fields of the response body are pretty much same. The only distinction is going to be in the section:

"values": [
    {
        "verify_status": true,
        "token_key": "sure1ff45jk7858b8ae88a77fa30"
    }
]

or,

"values": [
    {
        "update_status": true
    }
]

I have followed the Protocol Oriented Programming (POP) pattern for my use case.


Solution:

struct Value {
    let verifyStatus: Bool?
    let updateStatus: Bool?
    let tokenKey: String?
}
extension Value: Codable {
    private enum ValueCodingKeys: String, CodingKey {
        case verifyStatus = "verify_status"
        case updateStatus = "update_status"
        case tokenKey = "token_key"
    }
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: ValueCodingKeys.self)
        try container.encode(verifyStatus, forKey: .verifyStatus)
        try container.encode(updateStatus, forKey: .updateStatus)
        try container.encode(tokenKey, forKey: .tokenKey)
    }
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: ValueCodingKeys.self)
        verifyStatus = try container.decodeIfPresent(Bool.self, forKey: .verifyStatus)
        updateStatus = try container.decodeIfPresent(Bool.self, forKey: .updateStatus)
        tokenKey = try container.decodeIfPresent(String.self, forKey: .tokenKey)
    }
}

protocol Resultable {
    var values: [Value] { get }
}
struct Result: Resultable {
    let values: [Value]
}
extension Result: Codable {
    private enum ResultCodingKeys: String, CodingKey {
        case values
    }
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: ResultCodingKeys.self)
        try container.encode(values, forKey: .values)
    }
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: ResultCodingKeys.self)
        values = try container.decode([Value].self, forKey: .values)
    }
}

protocol Responsable {
    var code: Int { get }
    var status: String { get }
    var message: String { get }
    var result: Result { get }
}
struct Response: Responsable {
    let code: Int
    let status: String
    let message: String
    let result: Result
}
extension Response: Codable {
    private enum ResponeCodingKeys: String, CodingKey {
        case code
        case status
        case message
        case result
    }
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: ResponeCodingKeys.self)
        try container.encode(code, forKey: .code)
        try container.encode(status, forKey: .status)
        try container.encode(message, forKey: .message)
        try container.encode(result, forKey: .result)
    }
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: ResponeCodingKeys.self)
        code = try container.decode(Int.self, forKey: .code)
        status = try container.decode(String.self, forKey: .status)
        message = try container.decode(String.self, forKey: .message)
        result = try container.decode(Result.self, forKey: .result)
    }
}

I think I'm over-complicating the solution. Is there any better solution to achieve this?

Do I really have to have two protocol defined?

\$\endgroup\$
1
\$\begingroup\$

Your intuition is correct: you don't need either the Resultable or the Responsable protocol. The reason is that each protocol only has one type that conforms to it. Protocols are great for sharing functionality among multiple types (using protocol extensions) or defining a set of guaranteed properties and behaviors common to a group of types. But the benefits only come when there is more than one type conforming to the protocol. Since your code only has one type conforming to each protocol, it's better for now to not have the protocol.

Also, this isn't part of your question but it might be worth investigating whether your implementation of Response is tied too closely to the structure of your JSON. As it is, a client would have to dig pretty far into a response's result value to get any useful information out of it. Redefining Result could bring the information forward and make things much easier on the client. For example:

enum Result {
    case verify(status: Bool, tokenKey: String)
    case update(status: Bool)
}

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

The Codable implementation would be more complicated, but I think greater simplicity at the call site is worth the effort.

\$\endgroup\$
  • \$\begingroup\$ Well, that's a pretty good explanation about protocol. I appreciate your effort to look at this. But with your other proposal with enum got me something to be confused. What do I do with the values property inside the result property if I'm going to implement it your suggested way? More precisely, how would I deserialize the verifyStatus updateStatus tokenKey into the result? \$\endgroup\$ – nayem Feb 8 '18 at 17:17
  • \$\begingroup\$ I think that's best asked as a separate question on Stack Overflow. You'll probably need to do something like this: stackoverflow.com/a/45147203/277905 but you'll have to extract /inject the values from the json array when you decode/encode them. \$\endgroup\$ – proxpero Feb 9 '18 at 15:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.