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.
Related
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))
)
}
}
}
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()
}
I have a problem with parsing data from NBP api "http://api.nbp.pl/api/exchangerates/tables/a/?format=json" . I created struct CurrencyDataStore and Currency
struct CurrencyDataStore: Codable {
var table: String
var no : String
var rates: [Currency]
enum CodingKeys: String, CodingKey {
case table
case no
case rates
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
table = ((try values.decodeIfPresent(String.self, forKey: .table)))!
no = (try values.decodeIfPresent(String.self, forKey: .no))!
rates = (try values.decodeIfPresent([Currency].self, forKey: .rates))!
} }
struct Currency: Codable {
var currency: String
var code: String
var mid: Double
enum CodingKeys: String, CodingKey {
case currency
case code
case mid
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
currency = try values.decode(String.self, forKey: .currency)
code = try values.decode(String.self, forKey: .code)
mid = try values.decode(Double.self, forKey: .mid)
}
}
In controllerView class I wrote 2 methods to parse data from API
func getLatestRates(){
guard let currencyUrl = URL(string: nbpApiUrl) else {
return
}
let request = URLRequest(url: currencyUrl)
let task = URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) -> Void in
if let error = error {
print(error)
return
}
if let data = data {
self.currencies = self.parseJsonData(data: data)
}
})
task.resume()
}
func parseJsonData(data: Data) -> [Currency] {
let decoder = JSONDecoder()
do{
let currencies = try decoder.decode([String:CurrencyDataStore].self, from: data)
}
catch {
print(error)
}
return currencies
}
This code didn't work. I have this error "typeMismatch(Swift.Dictionary, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Dictionary but found an array instead.", underlyingError: nil))".
Could you help me?
The JSON being returned by that API gives you an array, not a dictionary, but you're telling the JSONDecoder to expect a dictionary type. Change that line to:
let currencies = try decoder.decode([CurrencyDataStore].self, from: data)
I am trying to pull car information from the following API.
but I can't seem to display the information in my tableview...
Any and all help is appreciated!
viewController
var hondaList: [HondaModel] = []
override func viewDidLoad() {
//let jsonUrl = "https://api.myjson.com/bins/149ex5"
let url = URL(string: "https://api.myjson.com/bins/149ex5")
URLSession.shared.dataTask(with: url!) { (data, urlrespone , error) in
do{
try self.hondaList = JSONDecoder().decode([HondaModel].self, from: data!)
for honda in self.hondaList {
print(honda.name)
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch{
print( "Error in fectching from https://api.myjson.com/bins/149ex5")
}
}.resume()
super.viewDidLoad()
}
Model
import Foundation
struct HondaModel: Decodable {
let name: String
let engine: String
let transmission: String
let ocolor: String
let icolor: String
let vin: String
}
This is a very common mistake: You are ignoring the root object (and both possible errors)
Add this struct
struct Root : Decodable {
private enum CodingKeys: String, CodingKey { case results = "Results", message = "Message" }
let results : [HondaModel]
let message : String
}
and decode
if let error = error { print(error); return }
do {
let root = try JSONDecoder().decode(Root.self, from: data!)
self.hondaList = root.results
...
and please, please, print the error rather than a meaningless literal string. The error tells you what's wrong.
catch {
print(error)
}
In your case you would get
"Expected to decode Array<Any> but found a dictionary instead."
which is a very significant hint.
try this
if let resultJSON = data?["Results"] as? [[String: Any]] {
do {
let _data = try JSONSerialization.data(withJSONObject: resultJSON, options: .prettyPrinted)
self.hondaList = try JSONDecoder().decode([HondaModel].self, from: _data)
// … same thing
}
}
Let's say you have some JSON:
{
"status": "error",
"data": {
"errormessage": "Could not get user with ID: -1.",
"errorcode": 14
}
}
For a given Error struct:
struct APIError: Decodable {
let code: Int?
let message: String?
enum CodingKeys: String, CodingKey {
case code = "errorcode"
case message = "errormessage"
}
}
Hit the web service, get the JSON, and initialize the struct:
let urlRequest = URLRequest(url: url)
let session = URLSession.shared
let task = session.dataTask(with: urlRequest)
{ (data, response, error) in
// Doesn't work because the portion of the JSON we want is in the "data" key
let e = try? JSONDecoder().decode(APIError.self, from: data)
}
task.resume()
Is there some easy way to do something like data["data"]? What's the correct model to follow?
Solution A - Convert the data to a JSON object, get the object we want, then convert it to a Data object and decode.
let jsonFull = (try? JSONSerialization.jsonObject(with: data, options: [])) as? [String: Any]
let json = jsonFull["data"]
let data_error = try? JSONSerialization.data(withJSONObject: json, options: [])
let e = try? JSONDecoder().decode(APIError.self, from: data_error)
Solution B - Wrap the target item in another struct
struct temp : Decodable {
let status: String?
let data: APIError?
}
let e = try? JSONDecoder().decode(temp.self, from: data).data
Solution C - Set the nested structure in decode (what if it is several objects deep?)
let e = try? JSONDecoder().decode([Any, APIError.self], from: data)
What patterns am I missing? What's the most elegant way to do this?
You can use the following approach:
struct APIError: Decodable {
let code: Int
let message: String
enum CodingKeys: String, CodingKey {
case data
}
enum ErrorCodingKeys: String, CodingKey {
case code = "errorcode"
case message = "errormessage"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let nestedContainer = try container.nestedContainer(keyedBy: ErrorCodingKeys.self, forKey: .data)
code = try nestedContainer.decode(Int.self, forKey: .code)
message = try nestedContainer.decode(String.self, forKey: .message)
}
}
let data = try! JSONSerialization.data(withJSONObject: ["status": "error", "data": ["errorcode": 14, "errormessage": "Could not get user with ID: -1."]], options: [])
let error = try! JSONDecoder().decode(APIError.self, from: data)