URLRequest Error "The given data was not valid JSON." - swift

Trying to make a POST request with headers and params
Codeable code:
struct WelcomeMessage: Codable {
let receivedMessages: [ReceivedMessage]
}
// MARK: - ReceivedMessage
struct ReceivedMessage: Codable, Identifiable {
let ackID: String
let message: Message
let id = UUID()
enum CodingKeys: String, CodingKey {
case ackID
case message
}
}
// MARK: - Message
struct Message: Codable {
let data, messageID, publishTime: String
enum CodingKeys: String, CodingKey {
case data
case messageID
case publishTime
}
}
Service code:
class GetMessages: ObservableObject {
private var project_id: String = "redacted"
private var project_name: String = "redacted"
#Published var messages = [ReceivedMessage]()
func getMessages() {
guard let url = URL(string: "https://pubsub.googleapis.com/v1/projects\(project_id)/subscriptions\(project_name):pull") else {return}
var request = URLRequest(url: url)
let parameters : [String:Any] = [
"returnImmediately": false,
"maxMessages": 10]
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("Bearer ya29.redacted", forHTTPHeaderField: "Authorization")
do {
request.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted)
} catch let error {
print(error.localizedDescription)
return
}
URLSession.shared.dataTask(with: request) { (data, response, error) in
guard error == nil else {print(error!.localizedDescription); return }
// guard let data = data else {print("empty data"); return }
let theData = try! JSONDecoder().decode(WelcomeMessage.self, from: data!)
print(theData)
DispatchQueue.main.async {
self.messages = theData.receivedMessages
}
}
.resume()
}
}
The response to the request should return some JSON data that looks like:
{
"receivedMessages": [
{
"ackId": "UdfdsfdsfdsfdsfdgfhgfjJHGkjkjhKgjhgjFhgfDFgfdgDFGDFdfgFDGfd",
"message": {
"data": "//BASE-64 ENCODED STRING HERE",
"messageId": "4130086024457484",
"publishTime": "2022-02-16T15:03:49.372Z"
}
}
]
}
Error message as above, not sure why it's saying the data is not valid JSON?
Additional opinionated question...Should I just be using AlamoFire for this?

If the json response you show is correct, then in ReceivedMessage change ackID to ackId (note the small "d"),
or use
enum CodingKeys: String, CodingKey {
case ackID = "ackId"
case message
}
Similarly for messageID in Message.

Related

Variable Not Passing In POST parameter in Swift

I'm trying to get the cities by country using the POSTMAN Api. When I don't bring any variables into the parameter, the request works as expected. Though, when I try to use a global variable as a parameter, it returns empty. It works perfectly fine if it was coded as such: "country": "Nigeria" (everything else the same)
Code below:
let myCountry = selectedCountryString.lowercased().trimmingCharacters(in: .whitespacesAndNewlines)
guard let url = URL(string: "https://countriesnow.space/api/v0.1/countries/population/cities/filter") else {
return
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let body: [String: Any] = [
"limit": 10,
"order": "dsc",
"orderBy": "value",
"country": "\(myCountry)"
]
request.httpBody = try? JSONSerialization.data(withJSONObject: body, options: .fragmentsAllowed)
let task = URLSession.shared.dataTask(with: request) {data, _, error in
guard let data = data, error == nil else {
return
}
do{
let response = try JSONDecoder().decode(CitiesPopModel.self, from: data)
onCompletion(response)
}
catch {
print("Error country -> \(myCountry)")
}
}
task.resume()
}
I switched my code to this and it is now working with the variable:
func callCitiesByPopAPI(completion: #escaping (CitiesPopModel?, Error?) -> ()) {
let url = "https://countriesnow.space/api/v0.1/countries/population/cities/filter"
let parameters: [String: Any] = [
"limit": 20,
"order": "dsc",
"orderBy": "value",
"country": selectedCountryString
]
AF.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: CitiesPopModel.self) { response in
if let error = response.error {
completion(nil, error)
return
}
if let result = response.value {
completion(result, nil)
print("City pop model result is \(result)")
return
}
}
}
Not worry about it you have to pass selectedCountryString value as like it then all goes perfectly
let selectedCountryString = "INDIA"
Replace value forHttpHeaderField for this:
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
And replace httpBody for this:
request.httpBody = body.percentEncoded()
I tested this with Algeria, it's worked.
extension Dictionary {
func percentEncoded() -> Data? {
map { key, value in
let escapedKey = "\(key)".addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) ?? ""
let escapedValue = "\(value)".addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) ?? ""
return escapedKey + "=" + escapedValue
}
.joined(separator: "&")
.data(using: .utf8)
}
}
extension CharacterSet {
static let urlQueryValueAllowed: CharacterSet = {
let generalDelimitersToEncode = ":#[]#" // does not include "?" or "/" due to RFC 3986 - Section 3.4
let subDelimitersToEncode = "!$&'()*+,;="
var allowed: CharacterSet = .urlQueryAllowed
allowed.remove(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)")
return allowed
}()
}
Here is my test code that shows how to fetch the data from the server,
using a variable as a parameter (country) to the POST request, and display it in a view. Works very well for me.
struct ContentView: View {
#State var cityPop = CitiesPopModel()
var body: some View {
List(cityPop.data) { data in
VStack {
Text(data.city).foregroundColor(.blue)
ScrollView(.horizontal) {
HStack {
ForEach(data.populationCounts) { pop in
VStack {
Text(pop.year)
Text(pop.sex)
Text(pop.value).foregroundColor(.blue)
}
}
}
}
}
}
.onAppear {
getCountry(country: "Australia") { result in
if let popData = result {
cityPop = popData
}
}
}
}
func getCountry(country: String, completion: #escaping(CitiesPopModel?) -> Void) {
guard let url = URL(string: "https://countriesnow.space/api/v0.1/countries/population/cities/filter") else {
return
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let body: [String: Any] = [
"limit": 10,
"order": "dsc",
"orderBy": "value",
"country": country
]
request.httpBody = try? JSONSerialization.data(withJSONObject: body, options: .fragmentsAllowed)
URLSession.shared.dataTask(with: request) { data, _, error in
guard let data = data, error == nil else { return }
do {
let response = try JSONDecoder().decode(CitiesPopModel.self, from: data)
return completion(response)
}
catch {
print("Error country -> \(country)")
}
completion(nil)
}.resume()
}
}
// MARK: - CitiesPopModel
struct CitiesPopModel: Codable {
var error: Bool = false
var msg: String = ""
var data: [City] = []
}
// MARK: - City
struct City: Identifiable, Codable {
let id = UUID()
let city: String
let country: String
let populationCounts: [Population]
enum CodingKeys: String, CodingKey {
case city, country, populationCounts
}
}
// MARK: - Population
struct Population: Identifiable, Codable {
let id = UUID()
let year, value, sex, reliabilty: String
enum CodingKeys: String, CodingKey {
case year, value, sex, reliabilty
}
}

Swift used Codable, but the type is not correct

I know that Codable = Decodable & Encodable but when calling json from xcode,
Codable was given as a struct, but an error saying
Argument type'login.Type' does not conform to expected type'Encodable' appears.
json code
struct login: Codable {
var userId: String?
var userPw: String?
class func LoginBoard(_ completeHandler: #escaping (login) -> Void) {
let loginboard: String = MAIN_URL + "/member/login"
guard let url = URL(string: loginboard) else {
print("Error: cannot create URL")
return
}
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = "POST"
urlRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
urlRequest.addValue("application/json", forHTTPHeaderField: "Accept")
urlRequest.httpBody = try? JSONEncoder().encode(login) // ERROR [Argument type 'login.Type' does not conform to expected type 'Encodable']
let session = URLSession.shared
let task = session.dataTask(with: urlRequest) { (data, response, error) in
guard error == nil else {
print("error calling Post on /todos/1")
print(error!)
return
}
guard let responseData = data else {
print("Error: did not receive data")
return
}
do {
let decoder = JSONDecoder.init()
let LoginList = try decoder.decode(login.self, from: responseData)
completeHandler(LoginList)
}
catch {
print("error trying to convert data to JSON")
return
}
}
task.resume()
}
There is no error in try decoder.decode
but only in urlRequest.httpBody = try? JSONEncoder().encode(login) what is the problem?
You need to have something like this to set the values.
let loginvalues = login(userId: "john", userPw: "adfadfa")
urlRequest.httpBody = try? JSONEncoder().encode(loginvalues)
If you place this inside a play ground and run it you will see that you get the json data.
struct Login: Codable {
var userId: String?
var userPw: String?
}
let loginvalues = Login(userId: "john", userPw: "adfadfa")
let test = try? JSONEncoder().encode(loginvalues)
print(String(data: test!, encoding: .utf8)!)

How do I add a body to an HTTP GET request made with Combine in Swift?

I am making an HTTP GET request using Combine and I don't know how to add a body. I know that it is not ok to have a body in a GET request, but I really need to test some things. I am using Xcode 11.4 and iOS 13.4. Stack Overflow doesn’t let me post this question unless i write some more information, but I can’t think of any more pieces of information that you might need for this. Here is my code:
import Foundation
import Combine
//MARK: - Object to retrieve from JSON
struct Doctor: Codable, Identifiable {
let id = UUID()
let patients: [Patients]
}
struct Patients: Codable, Identifiable {
let id: String
let name: String
let phone: String
enum CodingKeys: String, CodingKey {
case id = "_id"
case name
case phone
}
}
class Network {
enum Error: LocalizedError {
case invalidResponse
case addressUnreachable(URL)
var errorDescription: String? {
switch self {
case .invalidResponse:
return "The server responded with garbage."
case .addressUnreachable(let url):
return "\(url.absoluteString) is unreachable."
}
}
}
let urlRequest = URL(string: URL)!
let networkQueue = DispatchQueue(label: "Networking",
qos: .default,
attributes: .concurrent)
func downloadPatients() -> AnyPublisher<Doctor, Error> {
URLSession.shared
.dataTaskPublisher(for: urlRequest)
.receive(on: networkQueue)
.map(\.data)
.decode(type: Doctor.self, decoder: JSONDecoder())
.mapError { (error) -> Network.Error in
switch error {
case is URLError:
return Error.addressUnreachable(self.urlRequest)
default:
return Error.invalidResponse
}
}
.eraseToAnyPublisher()
}
}
let networkRequest = Network()
func loadPatients() {
cancelable = networkRequest.downloadPatients()
.sink(
receiveCompletion: {
receiveValue: { doctor in
self.localPatients = doctor.patients
self.isShowing = false
}
)
}
Thank you!
I'm not sure if can be done with dataTaskPublish for a Get. But You can customize your request with this kind of call:
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.httpBody = body
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
URLSession.shared.dataTask(with: request) { (data, response, error) in
if let error = error {
// Handle the error here
}
guard let data = data else { return }
let resData = try! JSONDecoder().decode(ServerMessage.self, from: data)
}.resume()
}

Data fetched via postman and URLSession is different in here http://173.249.20.137:9000/apiapp/coupon

http://173.249.20.137:9000/apiapp/coupon GET method .
when I request via URLSession and Postman I get two different results. Actually postman data is correct, but the URL session has always the same response whether I add or delete data it is not going to update. Can anybody please give a look. if it is happening with me only or something wrong at the backend server.
I have tested requesting data with URLSession.shared and postman.
I actually like to have the data I get via postman through URLSession request too.
func getAvailableCoupons(urlString:String, completion: #escaping (_
product: Any, _ error: Error?) -> Void){
guard let url = URL(string: urlString) else {return}
let task = URLSession.shared.dataTask(with: url) { (data, response,
error) in
let jsonDecoder = JSONDecoder()
guard let dataResponse = data,
error == nil else {
print(error?.localizedDescription ?? "Response Error")
return }
let statusCode = (response as! HTTPURLResponse).statusCode
let responseJSON = try? JSONSerialization.jsonObject(with: dataResponse, options: [])
if let responseJSON = responseJSON as? [String: Any] {
if statusCode == 200 {
do {
let jsonData = try JSONSerialization.data(withJSONObject: responseJSON, options: [])
let responseData = try jsonDecoder.decode(CoupensResponseModel.self, from:jsonData)
completion(responseData, nil)
} catch let error {
print("error when parshing json response \(error)")
completion(error, nil )
}
} else if statusCode == 404{
completion(" 404 not found", nil )
} else {
print("fatal error \(error?.localizedDescription ?? "big errror")")
}
}
}
task.resume()
}
import Foundation
// MARK: - CoupensResponseModel
struct CoupensResponseModel: Codable {
let couponDetails: [CouponDetail]?
enum CodingKeys: String, CodingKey {
case couponDetails = "coupon_details"
}
}
// MARK: - CouponDetail
struct CouponDetail: Codable {
let id: Int?
let vouchersusageSet: [VouchersusageSet]?
let couponCode: String?
let minimumSpend: Int?
let expiryDate, createdDate: String?
let discountPrice, discountPercent: Int?
let discountBasis: DiscountBasis?
let couponImage: String?
let couponType: String?
enum CodingKeys: String, CodingKey {
case id
case vouchersusageSet = "vouchersusage_set"
case couponCode = "coupon_code"
case minimumSpend = "minimum_spend"
case expiryDate = "expiry_date"
case createdDate = "created_date"
case discountPrice = "discount_price"
case discountPercent = "discount_percent"
case discountBasis = "discount_basis"
case couponImage = "coupon_image"
case couponType = "coupon_type"
}
}
enum DiscountBasis: String, Codable {
case amount = "amount"
case percent = "percent"
}
// MARK: - VouchersusageSet
struct VouchersusageSet: Codable {
let id: Int?
let itemObj: ItemObj?
let voucherObj: Int?
let sectionObj, categoryObj: Int?
}
// MARK: - ItemObj
struct ItemObj: Codable {
let id: Int?
let code, productName: String?
let productPrice, discountedPrice: Int?
let productDescription, itemImage: String?
let categoryObj: CouponCategoryObj?
enum CodingKeys: String, CodingKey {
case id, code
case productName = "product_name"
case productPrice = "product_price"
case discountedPrice = "discounted_price"
case productDescription = "product_description"
case itemImage = "item_image"
case categoryObj
}
}
// MARK: - CouponCategoryObj
struct CouponCategoryObj: Codable {
let id: Int?
let categoryCode, categoryName: String?
let categoryImage: CouponJSONNull?
let sectionObj: Int?
enum CodingKeys: String, CodingKey {
case id
case categoryCode = "category_code"
case categoryName = "category_name"
case categoryImage = "category_image"
case sectionObj
}
}
// MARK: - Encode/decode helpers
class CouponJSONNull: Codable, Hashable {
public static func == (lhs: CouponJSONNull, rhs: CouponJSONNull) ->
Bool {
return true
}
public var hashValue: Int {
return 0
}
public func hash(into hasher: inout Hasher) {
// No-op
}
public init() {}
public required init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if !container.decodeNil() {
throw DecodingError.typeMismatch(CouponJSONNull.self,
DecodingError.Context(codingPath: decoder.codingPath,
debugDescription:
"Wrong type for CouponJSONNull"))
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encodeNil()
}
}
Try the following method
let headers = [
"Cache-Control": "no-cache",
]
let request = NSMutableURLRequest(url: NSURL(string: "http://173.249.20.137:9000/apiapp/coupon")! as URL,
cachePolicy: .useProtocolCachePolicy,
timeoutInterval: 10.0)
request.httpMethod = "GET"
request.allHTTPHeaderFields = headers
let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
if (error != nil) {
print(error)
} else {
let string = String(data: data!, encoding: .utf8) ?? ""
print(string)
}
})
dataTask.resume()
Please look on response both are same.
Postman response :
https://jsoneditoronline.org/?id=7b94ef69a3344164aa4a96423fdbf9db
Code response :
https://jsoneditoronline.org/?id=6e5a7d221d9c4c818f1d46fc893031fe

POST method in Swift4 urlSession Decodable not working(What goes wrong here?)

I am trying to post 2 parameters (email: and password) to get a response from the server with detailed user information, I build API to handle this and get a good response using Postman, but when I tried to implement this with Swift4 new urlsession JSON decode and encode, it keeps failing and I got error on decoding the response data.
this my JSON response when using Postman:
{
"error": "false",
"message": "downloaded",
"UserInfo": {
"id": 5,
"email": "abc#hotmail.com",
"lastname": "Bence",
"name": "Mark",
"phone": "1234567",
"add1": "333",
"add2": "444",
"city": "ott",
"postalcode": "tttttt"
}
}
My struct file:
import UIKit
struct loginPost: Encodable {
let email: String
let password: String
}
struct User: Decodable {
let error: String?
let message: String?
let UserInfo: [UserData]
}
struct UserData: Codable {
let id: Int?
let email: String?
let lastname: String?
let name: String?
let phone: String?
let add1: String?
let add2: String?
let city: String
let postalcode: String?
}
My Function
func downloadJson() {
let url = URL(string: http://192.168.0.10/api/login_hashed.php)
guard let downloadURL = url else { return }
//POST Req
var request = URLRequest(url: downloadURL)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
let newpost = loginPost(email: "abc#hotmail.com", password: "123456")
do {
let jsonBody = try JSONEncoder().encode(newpost)
request.httpBody = jsonBody
print(jsonBody)
}catch{
print("some error")
}
URLSession.shared.dataTask(with: request) { data, urlResponse, error in
guard let data = data, error == nil, urlResponse != nil else {
print("something is wrong with url")
return
}
print("downloaded..")
do
{
let decoder = JSONDecoder()
let downloaduser = try decoder.decode(User.self, from: data)
self.logmessage = downloaduser.message!
print(self.logmessage)
DispatchQueue.main.async {
// self.tableView.reloadData()
}
} catch {
print("something wrong with decode")
}
}.resume()
}
I have figured it out finally, thanks,.
I just want to mention the cause of this error and share my experience.
The main cause is the way you send JSON and receive the incoming response. you should know exactly how the data look in order to create your struct the acceptable way.
My return data is just simple 2 line of text and array of text, my struct was:
import UIKit
struct loginPost: Encodable {
let email: String
let password: String
}
struct User: Decodable {
let error: String?
let message: String?
let UserInfo: [UserData]
}
struct UserData: Codable {
let id: Int?
let email: String?
let lastname: String?
let name: String?
let phone: String?
let add1: String?
let add2: String?
let city: String
let postalcode: String?
}
my mistake on line 18
let UserInfo: [UserData]
it should be
let UserInfo: UserData?
without the square bracket.
one more important point, always try to catch the decode error and it's dicription by implementing }catch let JsonErr {, it will give you exactly why the decode not working.
in my case:
downloaded.. something wrong after downloaded
typeMismatch(Swift.Array, Swift.DecodingError.Context(codingPath:
[h2h.User.(CodingKeys in _E33F61CC43E102744E4EF1B7E9D7EDDE).UserInfo],
debugDescription: "Expected to decode Array but found a
dictionary instead.", underlyingError: nil))
And make sure to make your server API to accept JSON format application/json and
decode what you send in order to receive what you looking for;
php code service API
$UserData = json_decode(file_get_contents("php://input"), true);
Simplest and easy way to decode the json.
MUST TRY
struct Welcome: Codable {
let error, message: String?
let userInfo: UserInfo?
enum CodingKeys: String, CodingKey {
case error, message
case userInfo = "UserInfo"
}
}
// MARK: - UserInfo
struct UserInfo: Codable {
let id: Int?
let email, lastname, name, phone: String?
let add1, add2, city, postalcode: String?
}
After that in your code , when you get response from api then write
let decoder = JSONDecoder()
let obj = try! decoder.decode(Welcome.self, from: jsonData!)
Print(obj)
List item
This Will work Are You Creating Model Is Wrong
struct loginPost: Encodable {
let email: String
let password: String
}
struct Users:Codable {
var error:String?
var message:String?
var UserInfo:UserDetails?
}
struct UserDetails:Codable {
let id: Int?
let email: String?
let lastname: String?
let name: String?
let phone: String?
let add1: String?
let add2: String?
let city: String
let postalcode: String?
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
downloadJson()
}
func downloadJson() {
let url = URL(string: "http://192.168.0.10/api/login_hashed.php")
guard let downloadURL = url else { return }
//POST Req
var request = URLRequest(url: downloadURL)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
let newpost = loginPost(email: "abc#hotmail.com", password: "123456")
do {
let jsonBody = try JSONEncoder().encode(newpost)
request.httpBody = jsonBody
print(jsonBody)
}catch{
print("some error")
}
URLSession.shared.dataTask(with: request) { data, urlResponse, error in
guard let data = data, error == nil, urlResponse != nil else {
print("something is wrong with url")
return
}
print("downloaded..")
do
{
let decoder = JSONDecoder()
let downloaduser = try decoder.decode(Users.self, from: data)
// self.logmessage = downloaduser.message!
// print(self.logmessage)
DispatchQueue.main.async {
// self.tableView.reloadData()
}
} catch {
print("something wrong with decode")
}
}.resume()
}
}