1

I need to know what is the best practise or best approach when API is returning text (String) for http status code 200

But also it is returning json object for http status code 400

I have networking layer built with native URLSession and using JSONDecoder for parsing the JSON

So when it comes to call the function it takes generic argument e.g.[Product] (Products array) object and it will give us products array

Again my question is is that API structured or made with good pracise and also what is best practise for this to parse the json with swift ios

EDITED - ADDITIONAL INFO

Let's imagine that you have api endpoint base_url/api/v1/get-otp-code and you are posting your phone number:

Method: POST 
url: base_url/api/v1/get-otp-code 
params: { "phone": "123456789" } 

And this endpoint will return json value if you did request the OTP previously Response: {"error":"some error", "message": "some message"}

But if you are requesting very first time it will give you string value Response: "dwaotpwaadd-dadwaawwdcwdadodwde"

So if you do not know what type will return you should make it dynamic

  • Any response could be any type, as long as that type is indicated in the `Content-Type` HTTP response header, and the client is appropriately expecting those types and honours the HTTP header… – deceze Dec 30 '20 at 13:43

1 Answers1

0

Hi @nikakirkitadze and welcome to our community!

As you already know every request may comes with an error so you have to handle the errors too in your generic method.

Lets say that you have the below generic method in order to perform get requests:

func getRequest<T: Codable, U: Codable>(completion: @escaping(_ response: T?, _ error: HTTPClientError<U>?) -> Void) {
        URLSession.shared.dataTask(with: URLRequest(url: URL(string: "")!)) { (data, response, error) in
            guard let statusCode = (response as? HTTPURLResponse)?.statusCode, let data = data else {
                completion(nil, HTTPClientError(type: .invalidResponse, model: nil))
                return
            }
            
            if statusCode == 400 {
                let decodedErrorData = try? JSONDecoder().decode(U.self, from: data)
                completion(nil, HTTPClientError(statusCode: statusCode, type: .AUTH_FAILED, model: decodedErrorData))
                return
            }
            
            // Success
            do {
                let decodedData = try JSONDecoder().decode(T.self, from: data)
                completion(decodedData, nil)
            } catch {
                print(error)
                let decodedErrorData = try? JSONDecoder().decode(U.self, from: data)
                completion(nil, HTTPClientError(statusCode: statusCode, type: .parsingError, model: decodedErrorData))
            }
            
        }.resume()
    }

The ExampleResponse model and the Generic HTTPClientError:

struct ExampleResponse: Codable { 
   // Add your coding keys here..
}

public final class HTTPClientError<T: Codable>: Error {
    
    public let statusCode: Int?
    public let type: Code
    public let model: T?
    
    public enum Code: Int {
        case none
        case invalidResponse
        case invalidRequest
        case parsingError
        case AUTH_FAILED = 401
        case FAILED = 500
        case SERVICE_UNAVAILABLE = 501
    }
    
    public required init(statusCode: Int? = nil, type: Code, model: T? = nil) {
        self.statusCode = statusCode
        self.type = type
        self.model = model
    }
}

As you can see we created a generic Error with a Generic Type as Encoding Type.

Now you can call your method like that:

func getExampleResponse(completion: @escaping(_ response: ExampleResponse?, _ error: HTTPClientError<String>?) -> Void) {
     getRequest(completion: completion)
}

So regarding your request and the error that you are waiting you can adjust the generic types in order to fit your needs.

The best practise here is:

  • Use Swift Codables in order to map your responses
  • Always check for errors if any
  • Create Generic method in order to avoid duplicate code

You can check my lightweight swift package about networking for additional help : https://github.com/kstefanou52/SKHTTPClient


Update for unknown response type

If you don't know the type of the response you have to try cast the response as shown below:

func getRequest(completion: @escaping(_ response: String?, _ error: [String: String]?) -> Void) {
        URLSession.shared.dataTask(with: URLRequest(url: URL(string: "")!)) { (data, response, error) in
            guard let statusCode = (response as? HTTPURLResponse)?.statusCode, let data = data else {
                completion(nil, ["error": "empty response"])
                return
            }
            
            if let stringResponse = String(data: data, encoding: .utf8) {
                completion(stringResponse, nil)
            } else if let dictionaryResponse = try? JSONDecoder().decode([String: String].self, from: data) {
                completion(nil, dictionaryResponse)
            } else {
                completion(nil, ["error": "unknown response"])
            }
            
        }.resume()
    }
kstefanou
  • 608
  • 10
  • 14
  • Thank you for for response I understand everything but what about when you have to parse plain text or json for base_urll/api/v1/products – nikakirkitadze Dec 30 '20 at 14:23
  • If you have plain text, then just replace ExampleResponse with String. if dictionary then replace ExampleResponse with [String: Any]. so simple :) – kstefanou Dec 30 '20 at 15:30
  • No, I think we are talking on different things Let's imagine that you have api endpoint base_url/api/v1/get-otp-code and you are posting your phone number Method: POST url: base_url/api/v1/get-otp-code params: { "phone": "123456789" } And this endpoint will return json value if you did request the otp previously Response: {"error":"some error", "message": "some message"} But if you are requesting very first time it will give you string value Response: "dwaotpwaadd-dadwaawwdcwdadodwde" So if you do not know what type will return you should make it dynamic – nikakirkitadze Dec 30 '20 at 19:13
  • Ok now it's more clear. This is a very bad practise from the server, anyway. In this case one option is to conditionally try to cast the response. Please check my updated answer for code. – kstefanou Dec 31 '20 at 09:16