I have created APINetworkManagerAll
here i have created serviceCall
and calling that in ViewController
. here postGenericCall2()
is called and response also coming but my given param
values not coming instead it says nil why? i am passing same values in postmn then response coming properly? where am i wrong? guide me please.
struct RequestObjectAll {
var params: [String: Any]? = nil
var method: HTTPMethod
var urlPath: String
var isTokenNeeded: Bool
var isLoaderNeed: Bool = false
var isErrorNeed: Bool = false
var vc: UIViewController?
}
class APINetworkManagerAll: NSObject {
static let sharedInstance = APINetworkManagerAll()
fileprivate override init() {
super.init()
}
func serviceCall<T: Decodable>(requestObject: RequestObjectAll, completion: @escaping (Result<T, Error>) -> Void) {
if requestObject.isLoaderNeed {
requestObject.vc?.showLoader()
}
guard let url = URL(string: requestObject.urlPath) else {
if requestObject.isLoaderNeed {
requestObject.vc?.hideLoader()
}
completion(.failure(NetworkError.invalidURL))
return
}
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = requestObject.method.rawValue
guard let httpBody = try? JSONSerialization.data(withJSONObject: requestObject.params ?? ["" : ""], options: []) else {
return
}
urlRequest.httpBody = httpBody
let task = URLSession.shared.dataTask(with: urlRequest) { data, _, error in
if requestObject.isLoaderNeed {
requestObject.vc?.hideLoader()
}
if let error = error {
completion(.failure(error))
return
}
if let data = data {
do {
let response = try JSONDecoder().decode(T.self, from: data)
completion(.success(response))
} catch {
completion(.failure(error))
}
} else {
let error = NSError(domain: "YourDomain", code: 0, userInfo: [NSLocalizedDescriptionKey: "No data received"])
completion(.failure(error))
}
}
task.resume()
}
}
And
func postGenericCall2(){
let param = ["name": "john",
"job": "AAA"
]
let requestObject = RequestObjectAll(
params: param,
method: .post,
urlPath: "https://reqres.in/api/users",
isTokenNeeded: false,
isLoaderNeed: true,
vc: self
)
APINetworkManagerAll.sharedInstance.serviceCall(requestObject: requestObject) { (result: Result<PostGenModelSmall, Error>) in
switch result {
case .success(let response):
// Handle a successful response
print("result of post service call.....: \(response)")
case .failure(let error):
// Handle the error
print("Error decoding JSON: \(error)")
}
}
}
struct PostGenModelSmall: Codable {
let name: String
let job: String
let id: String
let createdAt: String
}
error:
Error decoding JSON: keyNotFound(CodingKeys(stringValue: "name", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: "name", intValue: nil) ("name").", underlyingError: nil))
EDIT: if i write service call like this then i am getting response as postman. so is there any issue in above my APINetworkManagerAll
> serviceCall
method while adding urlRequest.httpBody = httpBody
body to urlRequest?
func postServiceCall(){
let params: [String : Any] = ["name": "john", "job": "AAA"]
var request = URLRequest(url: URL(string: "https://reqres.in/api/users")!)
request.httpMethod = "POST"
request.httpBody = try? JSONSerialization.data(withJSONObject: params, options: [])
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
DispatchQueue.global().async {
let session = URLSession.shared
let task = session.dataTask(with: request, completionHandler: { data, response, error -> Void in
print(response!)
do {
guard let data = data, error == nil else{
print(error?.localizedDescription ?? "No data")
return
}
let json = try JSONSerialization.jsonObject(with: data) as? [String : Any]
print("post data with response....\(json)")
} catch {
print("error")
}
})
task.resume()
}
}
o/p:
post data with response....Optional(["id": 582, "createdAt": 2024-03-12T06:28:51.904Z, "job": AAA, "name": john])
As a general rule, we should examine the response and make sure it is what we expected.
In this case, your JSON response to the POST
request with JSON body is very strange. It contains:
{
"{\"name\": \"john\",\"job\": \"AAA\"}": "",
"id": "100",
"createdAt": "2024-03-11T20:37:25.020Z"
}
It looks like it was not expecting JSON in the body of the request. It is a question of what your backend is expecting. In your case, as your particular backend appears to accept both application/x-www-form-urlencoded
and application/json
requests, you have two options:
You can set the Content-Type
header of your request to let it know that the payload is JSON:
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
do {
urlRequest.httpBody = try JSONSerialization.data(withJSONObject: requestObject.params ?? ["" : ""])
} catch {
completion(.failure(error)) // note, if you are going to exit, make sure to call completion handler
return
}
You can change the request to be an application/x-www-form-urlencoded
request:
// for the sake of clarity, you might want to set `Content-Type`
// even though it defaults to x-www-form-urlencoded
//
// request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.httpBody = Data("name=joe&job=AAA".utf8)
Note, your particular server defaults
But, as your code stands, the request is not well-formed (in the absence of a Content-Type
header of application/json
, it assumes it is application/x-www-form-urlencoded
, but the body is JSON), and not only did the backend not detect this, but it also returned a nonsensical response.
You really should fix the backend so that a malformed request returns a non-200 response, e.g., a 400 status code. And, often (in dev environments, at least) we would include a body (with some consistent “error” JSON payload) in that response that indicates the nature of the error, namely that name
and job
were not successfully parsed from the request. The specifics of how the backend should validate requests and report errors is beyond the scope of this question, but FYI.
If we replace the httpBody
with an application/x-www-form-urlencoded
sort of payload, like below, it worked correctly:
request.httpBody = Data("name=john&job=AAA".utf8)
It generated the response you were looking for:
{
"name":"john",
"job":"AAA",
"id":"859",
"createdAt":"2024-03-11T20:29:44.446Z"
}
Note, if your request must be in application/x-www-form-urlencoded
, make sure to property escape the values. See HTTP Request in Swift with POST method if doing this manually. Or use a framework, like Alamofire, which will greatly simply the creation of well-formed x-www-form-urlencoded
requests.
As an unrelated observation, I might suggest defining createdAt
to be a Date
:
struct PostGenModelSmall: Codable {
let name: String
let job: String
let id: String
let createdAt: Date
}
And, then when decoding, set a date formatter for the JSONDecoder
:
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSX"
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(formatter)
let response = try decoder.decode(PostGenModelSmall.self, from: data)
Then, it will parse the date for you.
By the way, you mentioned that you have a working Postman request. In Postman, you can tap on the </>
button, choose “Swift – URLSession” and it will show you a simple example of what the Swift code might look like:
Like most auto-generated code, the code is not great, but it will give you a starting place from which you can start to compare and contrast against your code. In this case, note the httpBody
of the request (and the setting of the Content-Type
header of the request, which is not required, but is best practice).
Obviously, when you do this, your request might be a little different, but hopefully it illustrates how you can use Postman features to see what the Swift code might look like.