cannot make post request in swift 4 - json

cannot men a POST request but cannot understand why, the struct is Codable and there is no erro in url. I get this message error in console
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** +[NSJSONSerialization dataWithJSONObject:options:error:]: Invalid top-level type in JSON write'
*** First throw call stack:
(0x1ac0dfea0 )
libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb)
but everything seems to be ok
my struct:
struct PostOfMine: Codable {
let body: String?
let id: Int?
let title: String?
let userId: Int?
}
my func:
func postData() {
guard let url = URL(string: "https://jsonplaceholder.typicode.com/posts") else {
print("WARNING: url related error")
return}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.addValue("Application/json", forHTTPHeaderField: "Content-Type")
let newPost = PostOfMine(body: "test body", id: 20, title: "test title", userId: 20)
do {
let jsonBody = try JSONSerialization.data(withJSONObject: newPost, options: [])
request.httpBody = jsonBody
} catch {
print(error.localizedDescription)
}
let session = URLSession.shared
let task = session.dataTask(with: request) { (Data, _, error) in
guard let data = Data else {return}
do {
let sentPost = try JSONSerialization.jsonObject(with: data, options: [])
print(sentPost)
} catch {
print(error.localizedDescription)
}
}
task.resume()
}

As per apple documents:
JSONSerialization.data(withJSONObject: obj, options: [])
If obj will not produce valid JSON, an exception is thrown. This exception is thrown prior to parsing and represents a programming error, not an internal error. You should check whether the input will produce valid JSON before calling this method by using isValidJSONObject(_:).
In your code exception is raised due to below reason
JSONSerialization.data(withJSONObject: obj, options: [])
In this method you have to pass the valid JSON object e.g obj. You are just confirming to the codable protocol and passing the structure variable instead of JSON object.
Code:
struct PostOfMine: Codable {
let body: String?
let id: Int?
let title: String?
let userId: Int?
private enum CodingKeys: String, CodingKey {
case body
case id
case title
case userId
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(body, forKey: .body)
try container.encode(id, forKey: .id)
try container.encode(title, forKey: .title)
try container.encode(userId, forKey: .userId)
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let newPost = PostOfMine(body: "test body", id: 20, title: "test title", userId: 20)
do {
let encoder = JSONEncoder()
let newPostData = try encoder.encode(newPost)
//Send newPostData to your server.
request.httpBody = newPostData
//Send data you your server
//For Decoding the data use JSONDecoder
let post = try JSONDecoder().decode(PostOfMine.self, from: newPostData)
debugPrint(post)
} catch {
debugPrint(error.localizedDescription)
}
}

Since your model conforms to Codable you get JSONEncoder().encode(_:) for free. Use that to encode instead of JSONSerialization
func postData() {
//...
//For this particular api, the server will take care of generating an `id` so leave that as `nil`
let newPost = PostOfMine(body: "test body", id: nil, title: "test title", userId: 20)
do {
let jsonBody = try JSONEncoder().encode(newPost)
} catch {
print(error.localizedDescription)
}
let session = URLSession.shared
session.dataTask(with: request) { _, response, error in
if error != nil {
//check & handle error from upload task.
}
if let response = response as? HTTPURLResponse {
// Monitor the status code recieved from the server. 200-300 = OK
print(response.statusCode)
// prints 201 - Created
}
}.resume()
}

Related

Swift - JSON decoding

I have a JSON response from an api call. The problem is I get different JSON responses depending on whether the user has entered the correct credentials or not. My question is how do I read and decode these responses to a useable struct and what is the best way to go about decoding these different responses. one thing I noticed is both response have a common "isSuccess" that may be useful. I have little to no experience with swift or reading JSON so this is all a learning experience for me.
This is the response for successful login
{"result":{"login":{"isAuthorized":true,"isEmpty":false,"userName":{"isEmpty":false,"name":{"firstName":"Jason","lastName":"Test","displayName":"Test, Jason","isEmpty":false,"fullName":"Jason Test"},"canDelete":false,"id":5793,"canModify":false},"username":"test#testable.com"},"parameters":{"isEmpty":false,"keep_logged_in_indicator":false,"username":"test#testable.com"}},"isAuthorized":true,"version":{"major":"2021","minor":"004","fix":"04","display":"2021.004.04","isEmpty":false},"isSystemDown":false,"timestamp":"2021-07-28T02:47:33Z","isSuccess":true}
This is the response for failure
{"isAuthorized":true,"version":{"major":"2021","minor":"004","fix":"04","display":"2021.004.04","isEmpty":false},"isSystemDown":false,"errors":[{"password":"Unable to login as 'test#testable.com'"}],"timestamp":"2021-07-28T02:47:05Z","isSuccess":false}
This is the code I have written for my api calls
func request<T: Decodable>(endPoint: EndPoint, method: Method, parameters: [String: Any]? = nil, completion: #escaping(Result<T, Error>) -> Void) {
// Creates a urlRequest
guard let request = createRequest(endPoint: endPoint, method: method, parameters: parameters) else {
completion(.failure(AppError.invalidUrl))
return
}
let session = URLSession.shared
session.dataTask(with: request) { data, response, error in
var results: Result<Data, Error>?
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
completion(.failure(AppError.badStatusCode))
return
}
if let response = response {
// Gets the JSESSIONID
let cookieName = "JSESSIONID"
if let cookie = HTTPCookieStorage.shared.cookies?.first(where: { $0.name == cookieName }) {
debugPrint("\(cookieName): \(cookie.value)")
}
print(response)
}
if let data = data {
results = .success(data)
// Converts data to readable String
let responseString = String(data: data, encoding: .utf8) ?? "unable to convert to readable String"
print("Server Response: \(responseString.description)")
} else if let error = error {
results = .failure(error)
print("Server Error: \(error.localizedDescription)")
}
DispatchQueue.main.async {
self.handleResponse(result: results, completion: completion)
}
}.resume()
}
private func handleResponse<T: Decodable>(result: Result<Data, Error>?, completion: (Result<T, Error>) -> Void) {
guard let result = result else {
completion(.failure(AppError.unknownError))
return
}
switch result {
case .success(let data):
do {
let json = try JSONSerialization.jsonObject(with: data, options: [])
print("Server JsonObject response: \(json)")
} catch {
completion(.failure(AppError.errorDecoding))
}
let decoder = JSONDecoder()
// Decodes that json data
do {
} catch {
}
case .failure(let error):
completion(.failure(error))
}
}
Im mostly interesting in being able to display the json error that occurs when credentials are incorrect. The deadline for my project Is slowing approaching and any help or suggestions would be much appreciated.
You can use Swift's Result type to differentiate a successful result from a failed result.
The Result type is not decodable by default so you will need to write a custom decoder like this:
struct Response: Decodable {
let result: Swift.Result<Result, Errors>
enum CodingKeys: String, CodingKey {
case isSuccess
case errors
case result
}
struct Result: Codable {
let login: Login
struct Login: Codable {
let isAuthorized: Bool
}
}
struct Errors: Error {
let contents: [[String: String]]
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if try container.decode(Bool.self, forKey: .isSuccess) {
result = .success(try container.decode(Result.self, forKey: .result))
} else {
result = .failure(
Errors(contents: try container.decode([[String: String]].self, forKey: .errors))
)
}
}
}

How to add parameters to JSON in function in swift

I have created one function for JSON parsing, which I am calling in every view controller, but i am unable to pass parameters from that function
i have created function in NSObject class:
func serviceCall(_ url: String, _ params:[String : Any], completion: #escaping (Data?, Error?) -> Void) {
let url = URL(string: url)!
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = "POST" //set http method as POST
do {
urlRequest.httpBody = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted) // pass dictionary to nsdata object and set it as request body
} catch let error {
print(error.localizedDescription)
}
URLSession.shared.dataTask(with: urlRequest) { data, response, error in
if let error = error {
completion(nil, error)
return
}
guard let data = data else {
preconditionFailure("No error was received but we also don't have data...")
}
completion(data, nil)
}.resume()
}
}
in registrationVC how to add parameters to this function
my parameters for registration service:
struct RegData: Codable {
var jsonrpc: String
var params: PostReg
}
struct PostReg: Codable{
var email: String
var password: String
var device_id: String
}
while calling serviceCall function how to add parameters to it
if i call like this in button action
let url = "https://e/api/reg"
let jsonpostParameters: [String: Any] = RegData(jsonrpc: "2.0", params: (PostLogin(email: nameTf.text!, password: passwordTf.text!, device_id: "2")))
self.fetch(url, jsonpostParameters) { (data: Data?, error: Error?) in
guard let dt = data else { return }
// convert data to JSON
print(dt)
error:
cannot convert a value [String:Any] to RegData
how to add RegData to serviceCall, shall i change serviceCall params type? if yes how..
how add RegData to serviceCall to parse JSON
Kindly try this for decode data
//Here ResponceData is your codable class
let dictData = try JSONSerialization.data(withJSONObject: data, options: .prettyPrinted)
let obj= try JSONDecoder().decode([ResponseData].self, from: dictData)

Alamofire JSON Response Error with post parameter

I use alamofire and swityjson, although I use it in the same way, I did not get any results here.
let exampleURl = URL(string: exampleUrl)!
let params: [String: String] = ["id": "expampleString"]
let headers: HTTPHeaders = [
"charset": "UTF-8",
"Accept": "application/json"
]
Alamofire.request(exampleURL, method: .post, parameters: params, encoding: JSONEncoding.default, headers: headers).validate(statusCode: 200..<600).responseJSON() { response
in
switch response.result {
case.success:
if let json = response.data {
do{
let data = try JSON(data: json)
let str = data
print(str["arrayName"])
let arrayData = str["arrayName"].arrayValue.map{$0["content"].stringValue}
print(arrayData[0])
let credit = arrayData[0]
}
catch{
print("JSON Error")
}
}
case .failure(let error):
print("RESPONSE ERROR: \(error)")
}
}
This is my Json output.
{"arrayName":[{"content":"Hello_World"}]}
This is Error. I don't understand. I send post parameters but i can't fetch parameter in Json array.
RESPONSE ERROR: responseSerializationFailed(reason: Alamofire.AFError.ResponseSerializationFailureReason.jsonSerializationFailed(error: Error Domain=NSCocoaErrorDomain Code=3840 "Invalid value around character 0." UserInfo={NSDebugDescription=Invalid value around character 0.}))
it seems like u used alamofire in the wrong way, try it out please:
here is the request:
let url = URL(string: "YOUR LINK HERE")
let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
let jsonDecoder = JSONDecoder()
let responseModel = try jsonDecoder.decode(BaseModel.self, from: data!)
}
task.resume()
here is your swift model classes:
import Foundation
struct ArrayName : Codable {
let content : String?
enum CodingKeys: String, CodingKey {
case content = "content"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
content = try values.decodeIfPresent(String.self, forKey: .content)
}
}
struct BaseModel : Codable {
let arrayName : [ArrayName]?
enum CodingKeys: String, CodingKey {
case arrayName = "arrayName"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
arrayName = try values.decodeIfPresent([ArrayName].self, forKey: .arrayName)
}
}
That error usually indicates you're not getting a JSON response. You need to debug that response, usually by printing it as a String or setting a breakpoint in the response handler and inspecting it there.

Swift 4 JSON decoding

I am trying to decode JSON. My swift function for decoding the JSON is:
func GetChapInfo(){
let endpoint = "https://chapel-logs.herokuapp.com/chapel"
let endpointUrl = URL(string: endpoint)
do {
var request = URLRequest(url: endpointUrl!)
request.httpMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
let task = URLSession.shared.dataTask(with: request){
(data: Data?, response: URLResponse?, error: Error?) in
let dataAsString = String(data: data!, encoding: .utf8)
//print(dataAsString)
if(error != nil) {
print("Error")
}
else{
do{
guard let chapData = try? JSONDecoder().decode(Chapel.self, from: data!) else {
print("Error: Couldn't decode data into chapData")
return
}
for E in chapData.chap {
print(E.Day as Any)
}
}
}
}
task.resume()
}
}
my struct in Swift is
struct Chapel: Decodable {
let chap: [Chap]
}
struct Chap: Decodable {
let Name: String?
let Loc: String?
let Year: Int?
let Month: Int?
let Day: Int?
let Hour: Int?
let Min: Int?
let Sec: Int?
}
and my response from the server is:
{"chap":{"Name":"Why Chapel","Loc":"FEC","Year":2018,"Month":9,"Day":4,"Hour":16,"Min":1,"Sec":7}}
However when I run this the program prints out "Error: Couldn't decode data into chapData" and I cannot figure out why.
First of all catch decoding errors. Never try?. The caught error is much more descriptive
Expected to decode Array<Any> but found a dictionary instead
Means: The value for key chap is a dictionary, not an array
struct Chapel: Decodable {
let chap: Chap
}
And then you have to print
print(chapData.chap.Day)
You can reduce your code. An explicit URLRequest and headers for a default GET request is not needed. This is sufficient:
let endpoint = "https://chapel-logs.herokuapp.com/chapel"
let endpointUrl = URL(string: endpoint)!
do {
let task = URLSession.shared.dataTask(with: endpointUrl) { (data, response, error) in
...

Posting user input JSON in Swift

I am trying to collect data from user and send it to an web service, however I get an error "invalid top-level type in JSON write" I am collecting the steps from the healthstore in another function and all the data is passed into the userhealthprofile variables correctly as it works printing them out. However something is wrong with my JSON code
Full error message is
2017-10-17 09:30:57.170950+0200 IphoneReader[347:40755] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** +[NSJSONSerialization dataWithJSONObject:options:error:]: Invalid top-level type in JSON write'
*** First throw call stack:
(0x1d0b3b3d 0x1c33b067 0x1d0b3a85 0x1da763c1 0x9aa50 0x9bb60 0x2231a3b5 0x2231a349 0x22304979 0x22321f87 0x2286bf1b 0x22868833 0x22868423 0x22867849 0x223146f5 0x222e62bb 0x22a797f7 0x22a7419b 0x22a7457d 0x1d06ffdd 0x1d06fb05 0x1d06df51 0x1cfc11af 0x1cfc0fd1 0x1e76bb41 0x22349a53 0xa4d18 0x1c7ae4eb)
libc++abi.dylib: terminating with uncaught exception of type NSException
Code
#IBAction func submitAction(sender: AnyObject) {
userHealthProfile.name = nameLabel.text
print(userHealthProfile.name)
userHealthProfile.age = Int(ageLabel.text!)
print(userHealthProfile.age)
userHealthProfile.heightInMeters = Int(heightLabel.text!)
print(userHealthProfile.heightInMeters)
userHealthProfile.weightInKilograms = Int(weightLabel.text!)
print(userHealthProfile.weightInKilograms)
for element in stepy.step {
print(element)
}
userHealthProfile.totalStepsCount = stepy.step
print("pressing button")
//create the url with URL
let url = URL(string: "www.thisismylink.com/postName.php")!
//change the url
//create the session object
let session = URLSession.shared
//now create the URLRequest object using the url object
var request = URLRequest(url: url)
request.httpMethod = "POST" //set http method as POST
do {
request.httpBody = try JSONSerialization.data(withJSONObject: userHealthProfile, options: .prettyPrinted) // pass dictionary to nsdata object and set it as request body
} catch let error {
print(error.localizedDescription)
}
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
//create dataTask using the session object to send data to the server
let task = session.dataTask(with: request as URLRequest, completionHandler: { data, response, error in
guard error == nil else {
return
}
guard let data = data else {
return
}
do {
//create json object from data
if let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] {
print(json)
// handle json...
}
} catch let error {
print(error.localizedDescription)
}
})
task.resume()
}
This is what userprofile looks like, this is what I need to send as a jsonobject for my web service.
class UserHealthProfile {
var name: String?
var age: Int?
var totalStepsCount: Array<Any>!
var heightInMeters: Int?
var weightInKilograms: Int?
}
I think you should use Alamofire for calling web API..
Here is the sample code according to your requirements.
I hope this will help you...
func Submit(parametersToSend:NSDictionary)
{
Alamofire.request(.POST, "http://www.thisismylink.com/postName.php",parameters: parametersToSend as? [String : AnyObject], encoding: .URL).responseJSON
{
response in switch response.2
{
case .Success(let JSON):
print(JSON)
let response = JSON as! NSDictionary
print("My Web Api Response: \(response)")
break
case .Failure(let error):
print("Request failed with error: \(error)")
break
}
}
}
Function Calling in your #IBAction func submitAction(sender: AnyObject) like this
#IBAction func submitAction(sender: AnyObject) {
let steps = stepy.step
let name = nameLabel.text!
let age = Int(ageLabel.text!)
let height = Int(heightLabel.text!)
let weight = Int(weightLabel.text!)
let parameters = [
"name": name
"age": age
"totalStepsCount": steps
"heightInMeters": height
"weightInKilograms": weight
]
self.submit(parametersToSend:parameters)
}