Swift - JSON decoding - json

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

Related

Fetching Data from API in Swift

So I am trying to fetch data from the Pokemon API, and I am getting stuck at the point where I am trying to decode the JSON into a struct. Here is my code:
{
"count":1118,
"next":"https://pokeapi.co/api/v2/pokemon/?offset=20&limit=20",
"previous":null,
"results":
[
{"name":"bulbasaur","url":"https://pokeapi.co/api/v2/pokemon/1/"},
{"name":"ivysaur","url":"https://pokeapi.co/api/v2/pokemon/2/"},
{"name":"venusaur","url":"https://pokeapi.co/api/v2/pokemon/3/"},
{"name":"charmander","url":"https://pokeapi.co/api/v2/pokemon/4/"},
{"name":"charmeleon","url":"https://pokeapi.co/api/v2/pokemon/5/"},
{"name":"charizard","url":"https://pokeapi.co/api/v2/pokemon/6/"},
{"name":"squirtle","url":"https://pokeapi.co/api/v2/pokemon/7/"},
{"name":"wartortle","url":"https://pokeapi.co/api/v2/pokemon/8/"},
{"name":"blastoise","url":"https://pokeapi.co/api/v2/pokemon/9/"},
{"name":"caterpie","url":"https://pokeapi.co/api/v2/pokemon/10/"},
{"name":"metapod","url":"https://pokeapi.co/api/v2/pokemon/11/"},
{"name":"butterfree","url":"https://pokeapi.co/api/v2/pokemon/12/"},
{"name":"weedle","url":"https://pokeapi.co/api/v2/pokemon/13/"},
{"name":"kakuna","url":"https://pokeapi.co/api/v2/pokemon/14/"},
{"name":"beedrill","url":"https://pokeapi.co/api/v2/pokemon/15/"},
{"name":"pidgey","url":"https://pokeapi.co/api/v2/pokemon/16/"},
{"name":"pidgeotto","url":"https://pokeapi.co/api/v2/pokemon/17/"},
{"name":"pidgeot","url":"https://pokeapi.co/api/v2/pokemon/18/"},
{"name":"rattata","url":"https://pokeapi.co/api/v2/pokemon/19/"},
{"name":"raticate","url":"https://pokeapi.co/api/v2/pokemon/20/"}
]
}
func fetchPokemon() {
let defaultSession = URLSession(configuration: .default)
if let url = URL(string: "https://pokeapi.co/api/v2/pokemon/") {
let request = URLRequest(url:url)
let dataTask = defaultSession.dataTask(with: request, completionHandler: { (data, response, error) -> Void in
guard error == nil else {
print ("error: ", error!)
return
}
guard data != nil else {
print("No data object")
return
}
guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else {
print("response is: ", response!)
return
}
guard let mime = response?.mimeType, mime == "application/json" else {
print("Wrong MIME type!")
return
}
DispatchQueue.main.async {
guard let result = try? JSONDecoder().decode(PokemonList.self, from: data!) else {
print("Error Parsing JSON")
return
}
let pokemon = result.pokemon
self.Pokemon = pokemon
print(self.Pokemon)
}
})
dataTask.resume()
}
}
and here is the pokemon struct:
struct Pokemon {
// Various properties of a post that we either need or want to display
let name: String
let url: String
}
extension Pokemon: Decodable {
// properties within a Post returned from the Product Hunt API that we want to extract the info from.
enum PokemonKeys: String, CodingKey {
// first three match our variable names for our Post struct
case name = "name"
case url = "url"
}
init(from decoder: Decoder) throws {
let postsContainer = try decoder.container(keyedBy: PokemonKeys.self)
name = try postsContainer.decode(String.self, forKey: .name)
url = try postsContainer.decode(String.self, forKey: .url)
}
}
struct PokemonList: Decodable {
var pokemon: [Pokemon]
}
It keeps reaching the point when decoding which says "Error Parsing JSON". I'm assuming that there may be an error in how I setup the pokemon struct?
Any ideas?
you are getting a parse error because the data model is not the same. your struct should be:
struct PokemonList: Decodable {
var results: [Pokemon]
var count: Int
var next: String
}
you don't need the extension.

Could not cast value of type 'NSNull' to 'NSString' and then the app crashes

We are trying to make a function to get JSON from an API..We know that this is giving us NIL but we dont know why the error is occuring. The exact error message that we got was
[]
2020-08-01 16:29:26.501199-0400 HEFT[97766:2952325] [] nw_proxy_resolver_create_parsed_array [C1 proxy pac] Evaluation error: NSURLErrorDomain: -1003
Could not cast value of type 'NSNull' (0x7fff87a92380) to 'NSString' (0x7fff87b502e8).
2020-08-01 16:29:26.670549-0400 HEFT[97766:2952139] Could not cast value of type 'NSNull' (0x7fff87a92380) to 'NSString' (0x7fff87b502e8).
(lldb)
We have tried messing around the code to find a solution and we tried to use some other questions but none of them were related with what we were trying to achieve.
func getJson() {
if let url = URL(string: "https://api.weather.gov/alerts/active?area=GA") {
URLSession.shared.dataTask(with: url) { (data:Data?, response:URLResponse?, error:Error?) in
if error == nil {
if data != nil {
if let json = try? JSONSerialization.jsonObject(with: data!, options: []) as? [String:AnyObject] {
DispatchQueue.main.async {
//if let rawfeatures = json["features"] {
var rawfeatures = json["features"] as! [Dictionary< String, AnyObject>]
var keepgoingfeatures = rawfeatures.count
var FeatureIndex = 0
while keepgoingfeatures != 0{
let currentRawFeature = rawfeatures[FeatureIndex]
let currentRawFeatureProperties = currentRawFeature["properties"]
let currentFeature = Feature()
currentFeature.event = currentRawFeatureProperties!["event"] as! String
currentFeature.description = currentRawFeatureProperties!["description"] as! String
currentFeature.instructions = currentRawFeatureProperties!["instruction"] as! String
currentFeature.urgency = currentRawFeatureProperties!["urgency"] as! String
keepgoingfeatures -= 1
FeatureIndex += 1
}
}
}
}
} else {
print("We have an error")
}
}.resume()
}
}
Some of these alerts have null for instructions. I’d suggest defining your object to acknowledge that this field is optional, i.e. that it might not be present. E.g.
struct Feature {
let event: String
let description: String
let instruction: String?
let urgency: String
}
And, when parsing it, I might suggest getting rid of all of those forced unwrapping operators, e.g.
enum NetworkError: Error {
case unknownError(Data?, URLResponse?)
case invalidURL
}
#discardableResult
func getWeather(area: String, completion: #escaping (Result<[Feature], Error>) -> Void) -> URLSessionTask? {
// prepare request
var components = URLComponents(string: "https://api.weather.gov/alerts/active")!
components.queryItems = [URLQueryItem(name: "area", value: area)]
var request = URLRequest(url: components.url!)
request.setValue("(\(domain), \(email))", forHTTPHeaderField: "User-Agent")
// perform request
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard
error == nil,
let httpResponse = response as? HTTPURLResponse,
200 ..< 300 ~= httpResponse.statusCode,
let responseData = data,
let responseDictionary = try? JSONSerialization.jsonObject(with: responseData) as? [String: Any],
let rawFeatures = responseDictionary["features"] as? [[String: Any]]
else {
DispatchQueue.main.async {
completion(.failure(error ?? NetworkError.unknownError(data, response)))
}
return
}
let features = rawFeatures.compactMap { feature -> Feature? in
guard
let properties = feature["properties"] as? [String: Any],
let event = properties["event"] as? String,
let description = properties["description"] as? String,
let urgency = properties["urgency"] as? String
else {
print("required string absent!")
return nil
}
let instruction = properties["instruction"] as? String
return Feature(event: event, description: description, instruction: instruction, urgency: urgency)
}
DispatchQueue.main.async {
completion(.success(features))
}
}
task.resume()
return task
}
A few other observations:
I’ve removed all of the forced casting (the as!). You don’t want your app crashing if there was some problem in the server. For example, not infrequently I receive a 503 error. You don’t want to crash if the server is temporarily unavailable.
The docs say that you should set the User-Agent, so I’m doing that above. Obviously, set the domain and email string constants accordingly.
While you can build the URL manually, it’s safest to use URLComponents, as that will take care of any percent escaping that might be needed. It’s not needed here, but will be a useful pattern if you start to get into more complicated requests (e.g. need to specify a city name that has a space in it, such as “Los Angeles”).
I’d suggest the above completion handler pattern so that the caller can know when the request is done. So you might do something like:
getWeather(area: "GA") { result in
switch result {
case .failure(let error):
print(error)
// update UI accordingly
case .success(let features):
self.features = features // update your model object
self.tableView.reloadData() // update your UI (e.g. I'm assuming a table view, but do whatever is appropriate for your app
}
}
I’m returning the URLSessionTask in case you might want to cancel the request (e.g. the user dismisses the view in question), but I’ve marked it as a #discardableResult, so you don’t have to use that if you don’t want.
I’ve replaced the tower of if statements with a guard statement. It makes the code a little easier to follow and adopts an “early exit” pattern, where you can more easily tie the exit code with the failure (if any).
Personally, I’d suggest that you take this a step further and get out of manually parsing JSONSerialization results. It’s much easier to let JSONDecoder do all of that for you. For example:
struct ResponseObject: Decodable {
let features: [Feature]
}
struct Feature: Decodable {
let properties: FeatureProperties
}
struct FeatureProperties: Decodable {
let event: String?
let description: String
let instruction: String?
let urgency: String
}
enum NetworkError: Error {
case unknownError(Data?, URLResponse?)
case invalidURL
}
#discardableResult
func getWeather(area: String, completion: #escaping (Result<[FeatureProperties], Error>) -> Void) -> URLSessionTask? {
var components = URLComponents(string: "https://api.weather.gov/alerts/active")!
components.queryItems = [URLQueryItem(name: "area", value: area)]
var request = URLRequest(url: components.url!)
request.setValue("(\(domain), \(email))", forHTTPHeaderField: "User-Agent")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard
error == nil,
let httpResponse = response as? HTTPURLResponse,
200 ..< 300 ~= httpResponse.statusCode,
let responseData = data
else {
DispatchQueue.main.async {
completion(.failure(error ?? NetworkError.unknownError(data, response)))
}
return
}
do {
let responseObject = try JSONDecoder().decode(ResponseObject.self, from: responseData)
DispatchQueue.main.async {
completion(.success(responseObject.features.map { $0.properties }))
}
} catch let parseError {
DispatchQueue.main.async {
completion(.failure(parseError))
}
}
}
task.resume()
return task
}
The short answer is because you force cast everything and assume a very specific format which the json doesnt have.
so at some point you read a value that just insnt there.
Concretely instruction.
as a working/non crashing fix (which I locally ran!):
let currentFeature = Feature()
currentFeature.event = currentRawFeatureProperties!["event"] as? String ?? ""
currentFeature.description = currentRawFeatureProperties!["description"] as? String ?? ""
currentFeature.instructions = currentRawFeatureProperties!["instruction"] as? String ?? ""
currentFeature.urgency = currentRawFeatureProperties!["urgency"] as? String ?? ""
I'd urge you to refactor your function broadly

Writing a JSON serialization function

I'd like to make a function that takes in a few parameters and then outputs the data I need from a web API. Obviously a good deal of the time I'll need to customize it to suit the use case but just for fun I'm trying to figure out a super basic function the successfully parses JSON, as about half of the lines of code in the function below are generic error handling.
For example if I generally use something like
func getJSON(completionHandler: #escaping (Bool) -> ()) {
let jsonUrlString = "https://api.nytimes.com/svc/topstories/v1/business.json?api-key=f4bf2ee721031a344b84b0449cfdb589:1:73741808"
guard let url = URL(string: jsonUrlString) else {return}
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data, err == nil else {
print(err!)
return
}
do {
let response = try
JSONDecoder().decode(TopStoriesResponse.self, from: data)
self.storyData = response.results
completionHandler(true)
} catch let jsonErr {
print("Error serializing JSON", jsonErr)
}
}.resume()
}
The only three things that will change from case to case (again, in the most absolutely basic of scenarios) are the url link to the API, the Struct that I set up to look for the pieces of data I need, and the array that I output the results to once the data request is finished.
Could I trim the fat on that and do something like
func jsonFetcher(apiLink: String, structToDecode: String, arrayThatHoldsResponse: [String], completionHandler: #escaping (Bool) -> ()) {
let jsonUrlString = apiLink
guard let url = URL(string: jsonUrlString) else {return}
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data, err == nil else {
print(err!)
return
}
do {
let response = try
JSONDecoder().decode(structToDecode, from: data)
arrayThatHoldsResponse = response.results
completionHandler(true)
} catch let jsonErr {
print("Error serializing JSON", jsonErr)
}
}.resume()
}
I'm just not sure about the data types of structToDecode and arrayThatHoldsResponse (in the example function above I just using String as a placeholder), assuming they look like
Struct(s)
struct TopStoriesResponse: Decodable {
let status: String
let results: [Story]
}
struct Story: Decodable {
let title: String
let abstract: String
let url: String
let multimedia: [Multimedia]
private enum CodingKeys: String, CodingKey {
case title
case abstract
case url
case multimedia
}
init(from decoder:Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
title = try container.decode(String.self, forKey: .title)
abstract = try container.decode(String.self, forKey: .abstract)
url = try container.decode(String.self, forKey: .url)
multimedia = (try? container.decode([Multimedia].self, forKey: .multimedia)) ?? []
}
}
Array
var storyData = [Story]()
This way I can just call
jsonFetcher(apiLink: link, structToDecode: myStruct, arrayThatHoldsResponse: myArray, completionHandler: <#T##(Bool) -> ()#>)
Thanks for any help!
The power of generics. You can make a generic function, where the parameter is the urlString. The T inheritance the Decodable protocol.
This way you can call this function everytime as long as your Model inheritance the Decodable protocol.
func fetchData<T: Decodable>(urlString: String, completion: #escaping (T) -> ()) {
let url = URL(string: urlString)!
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let error = error {
print(error.localizedDescription)
}
guard let data = data else { return }
do {
let object = try JSONDecoder().decode(T.self, from: data)
completion(object)
} catch let jsonErr {
print("Failed to decode json:", jsonErr)
}
}.resume()
}
How to call the function:
struct User: Decodable { }
fetchData(urlString: "yourUrl") { (User: User) in
// Handle result
}
struct Animal: Decodable { }
fetchData(urlString: "yourUrl") { (animal: Animal) in
// Handle result
}
// Or if you want to fetch an array of users instead
fetchData(urlString: "yourUrl") { (users: [User]) in
// Handle result
}
In your case
var storiesData: [Story] = []
fetchData(urlString: "https://api.nytimes.com/svc/topstories/v1/business.json?api-key=f4bf2ee721031a344b84b0449cfdb589:1:73741808") { (stories: [Story] in
storiesData = stories
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
Based on Jacob's answer I recommend to return also a possible error.
To keep the generic layout declare an – also generic – enum as return type
enum FetchResult<T> {
case success(T), failure(Error)
}
and return FetchResult with the passed static type
func fetchData<T: Decodable>(url: URL, completion: #escaping (FetchResult<T>) -> Void) {
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else {completion(.failure(error!)); return }
do {
let object = try JSONDecoder().decode(T.self, from: data)
completion(.success(object))
} catch {
completion(.failure(error))
}
}.resume()
}
and use it
let jsonUrl = URL(string: "https://api.nytimes.com/svc/topstories/v1/business.json?api-key=••••••••••••••••••:1:73741808")!
fetchData(url: jsonUrl) { (result : FetchResult<TopStoriesResponse>) in
switch result {
case .success(let object): print(object) // do something with object
case .failure(let error): print(error) // handle the error
}
}

How should I go about parsing a JSON response from an API with Alamofire and SwiftyJSON?

I am trying to parse a JSON from an API using Alamofire and SwiftyJSON, but am having trouble trying to access the information in the JSON. I need to simply parse the JSON for an item called "ask_price": and also "time_coinapi" but I am not sure how I manage the response, or if I have to use a different method. here is what I have at the moment:
class CoinAPIManager {
var prices: [String] = []
var times: [String] = []
static let shared = CoinAPIManager()
func getReq() {
let headers: HTTPHeaders = [
"X-CoinAPI-Key": "Key"
]
Alamofire.request("https://rest.coinapi.io/v1/quotes/BITSTAMP_SPOT_BTC_USD/history?time_start=2018-08-21T00:00:00&time_end=2018-08-22T00:00:00&limit=100", headers: headers).responseJSON { response in
debugPrint(response)
if let data = try? String(contentsOf: response) {
let json = JSON(parseJSON: data)
parse(json: json)
}
}
func parse(json: JSON) {
for result in json[].arrayValue {
let price = result["ask_price"].stringValue
}
}
}
}
and I have also tried this:
func getReq() {
let headers: HTTPHeaders = [
"X-CoinAPI-Key": "Key"
]
Alamofire.request("https://rest.coinapi.io/v1/quotes/BITSTAMP_SPOT_BTC_USD/history?time_start=2018-08-21T00:00:00&time_end=2018-08-22T00:00:00&limit=100", headers: headers).responseJSON { response in
debugPrint(response)
switch response.result {
case .failure(let error):
// Do whatever here
return
case .success(let data):
// First make sure you got back a dictionary if that's what you expect
guard let json = data as? [String : AnyObject] else {
print("Failed to get expected response from webserver.")
return
}
// Then make sure you get the actual key/value types you expect
guard var price = json["ask_price"] as? Double else {
print("Failed to get data from webserver")
return
}
}
What am I doing wrong? this is how the JSON looks:
[
{
"symbol_id": "BITSTAMP_SPOT_BTC_USD",
"time_exchange": "2013-09-28T22:40:50.0000000Z",
"time_coinapi": "2017-03-18T22:42:21.3763342Z",
"ask_price": 770.000000000,
"ask_size": 3252,
"bid_price": 760,
"bid_size": 124
},
{
"symbol_id": "BITSTAMP_SPOT_BTC_USD",
"time_exchange": "2013-09-28T22:40:50.0000000",
"time_coinapi": "2017-03-18T22:42:21.3763342",
"ask_price": 770.000000000,
"ask_size": 3252,
"bid_price": 760,
"bid_size": 124
}
]
previous question deleted and reposted due to large mistake
you need to change your response to SwiftyJSON object like this
Alamofire.request("https://rest.coinapi.io/v1/quotes/BITSTAMP_SPOT_BTC_USD/history?time_start=2018-08-21T00:00:00&time_end=2018-08-22T00:00:00&limit=100", headers: headers).responseJSON { response in
debugPrint(response)
switch response.result {
case .failure(let error):
// Do whatever here
return
case .success:
// First make sure you got back a dictionary if that's what you expect
let responseJSON = JSON(response.result.value!)
if responseJSON.count != 0 {
print(responseJSON)
//do whatever you want with your object json
}
}
}
i suggest in your ApiManager you can use completion blocks to manage asyncronous request, check the next code.
class func getRequestWithoutParams(didSuccess:#escaping (_ message: JSON) -> Void, didFail: #escaping (_ alert:UIAlertController)->Void){
Alamofire.request("http://foo.bar"),method: .post,parameters: parameters,encoding: JSONEncoding.default,headers:nil).responseJSON { response in
switch response.result{
case .success:
let res = JSON(response.result.value!)
didSuccess(res)
break
case .failure(let error):
let alert = UIAlertController(title: "Error", message: error.localizedDescription, preferredStyle: .alert)
let done = UIAlertAction(title: "OK", style: .default, handler: nil)
alert.addAction(done)
didFail(alert)
}
}
}
From Swift 4, you should be able to use codable to solve it:
struct YourStructure: Codable {
let symbol_id: String?
let time_exchange: String?
let ask_price: String?
private enum CodingKeys: String, CodingKey {
case symbol_id = "symbol_id"
case time_exchange = "time_exchange"
case ask_price = "ask_price"
}
}
And then parse it with JSONDecoder
let decoder = JSONDecoder()
let parsedData = decoder.decode(YourStructure.self, from: "YourJsonData")

How do I get values from a complex JSON object?

Is it possible that someone could show me how to get the names of these pizza places printing out? My application prints out the expected "Status Code: 200". However, my console only shows empty brackets []. I suspect that I am not pulling values from my JSON object properly.
I'm using this link for my API.
Link For API
Question
How can I properly fetch values from my serialized JSON object?
relevant code:
// Response
if let httpResponse = response as? NSHTTPURLResponse where httpResponse.statusCode == 200, let data = data {
print("Status Code: \(httpResponse.statusCode)")
do {
let json = try NSJSONSerialization.JSONObjectWithData(data, options: .MutableContainers)
if let pizzaPlaces = json["response"] as? [[String: AnyObject]] {
for place in pizzaPlaces {
if let name = place ["name"] as? String {
self.PizzaClass.append(name)
}
}
}
} catch {
print("Error Serializing JSON Data: \(error)")
}
print(self.PizzaClass)
}
}).resume()
You need to cast your NSJSONSerialization.JSONObjectWithData result as a [String:AnyObject].
let jsonObject = try NSJSONSerialization.JSONObjectWithData(returnedData, options: .MutableLeaves) as! [String: AnyObject]
Once you have that all you need to do is pay attention to what you're casting. Take the code below for an example. If we want to get our response object using jsonObject["response"] what kind of data structure do we have?
"response": {
"venues": [{
//... continues
}]
}
On the left we have "response" which is a string, on the right we have {} which is an AnyObject. So we have [String: AnyObject]. You just need to think about what object your dealing with piece by piece. Below is a working example that you can just paste into your application.
full working code:
func getJson() {
let request = NSMutableURLRequest(URL: NSURL(string: "https://api.foursquare.com/v2/venues/search?client_id=0F5M0EYOOFYLBXUOKTFKL5JBRZQHAQF4HEM1AG5FDX5ABRME&client_secret=FCEG5DWOASDDYII4U3AAO4DQL2O3TCN3NRZBKK01GFMVB21G&v=20130815%20&ll=29.5961,-104.2243&query=burritos")!)
let session = NSURLSession.sharedSession()
request.HTTPMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Accept")
let task = session.dataTaskWithRequest(request) { (data: NSData?, response: NSURLResponse?, error: NSError?) -> Void in
guard let testResponse = response as? NSHTTPURLResponse else {
print("\(response)")
return
}
guard let status = HTTPStatusCodes(rawValue: testResponse.statusCode) else {
print("failed to unwrap status")
return
}
print(status)
switch status {
case .Created:
print("ehem")
case .BadRequest:
print("bad request")
case .Ok:
print("ok")
guard let returnedData = data else {
print("no data was returned")
break
}
do {
let jsonObject = try NSJSONSerialization.JSONObjectWithData(returnedData, options: .MutableLeaves) as! [String: AnyObject]
guard let response = jsonObject["response"] as? [String: AnyObject] else { return }
guard let venues = response["venues"] as? [AnyObject] else { return }
guard let location = venues[0]["location"] as? [String:AnyObject] else { return }
guard let formattedAddress = location["formattedAddress"] else { return }
print("response: \n\n \(response)\n------")
print("venues : \n\n \(venues)\n-------")
print("location : \n\n \(location)\n------")
print("formatted address : \n \(formattedAddress)")
} catch let error {
print(error)
}
// update user interface
dispatch_sync(dispatch_get_main_queue()) {
print("update your interface on the main thread")
}
}
}
task.resume()
}
place this either in its own file our outside of the class declaration,
enum HTTPStatusCodes : Int {
case Created = 202
case Ok = 200
case BadRequest = 404
}
Not that this was what you are looking for, but since you are new to Swift take a look at Alamofire. It handles JSON serialization for you. And when you need to chain calls PromiseKit is super slick.
Alamofire.request(.GET, url).responseJSON {response in
switch (response.result) {
case .Success(let value):
let pizzas = JSON(value).arrayValue
for place in pizzaPlaces {
if let name = place ["name"] as? String {
self.PizzaClass.append(name)
}
}
case .Failure(let error):
if let data = response.data, let dataString = String(data: data, encoding: NSUTF8StringEncoding) {
print("ERROR data: \(dataString)")
}
print("ERROR: \(error)")
}
}