Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
218 views
in Technique[技术] by (71.8m points)

swift - REST-API Returns plain text for status code 200 and json for status code 400

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


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...