How do I get values from a complex JSON object? - json

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

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

JSON SWIFT, how to access the values

i have the following Json
USD {
"avg_12h" = "8252.96";
"avg_1h" = "8420.80";
"avg_24h" = "8253.11";
"avg_6h" = "8250.76";
rates = {
last = "8635.50";
};
"volume_btc" = "76.05988903";
}
where USD is a key found after searching in a json file, i want to access "avg_12h" value and assign it to a variable, what is the best way to do it.
import UIKit
/*URLSessionConfiguration.default
URLSessionConfiguration.ephemeral
URLSessionConfiguration.background(withIdentifier: <#T##String#>)
// create a URLSession instance
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)*/
/*create a URLSession instance*/
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
/*
The session.dataTask(with: url) method will perform a GET request to the url specified and its completion block
({ data, response, error in }) will be executed once response is received from the server.*/
let url = URL(string: "https://localbitcoins.com/bitcoinaverage/ticker-all-currencies")!
let task = session.dataTask(with: url) { data, response, error in
// ensure there is no error for this HTTP response
guard error == nil else {
print ("error: \(error!)")
return
}
// ensure there is data returned from this HTTP response
guard let content = data else {
print("No data")
return
}
/*JSONSerialization.jsonObject(with: content,
options: JSONSerialization.ReadingOptions.mutableContainers) as?
[String: Any] will parse the JSON data returned from web server into a dictionary*/
// serialise the data / NSData object into Dictionary [String : Any]
guard let json = (try? JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers)) as? [String: Any] else {
print("Not containing JSON")
return
}
let bolivares = "VES"
for (key, value) in json {
if key==bolivares {
print(value)
//ADD CODE TO ACCESS avg_12h and assign it to a value
}
}
}
// update UI using the response here
// execute the HTTP request
task.resume()
Assuming you are receiving the JSON as raw data and it hasn't been converted to an object yet, ou would want to do something like the following:
guard let jsonObject = try? JSONSerialization.jsonObject(with: data, options: []) as! [String:[String]] else { return }
let usd = jsonObject["USD"]
let avg_12h = usd["avg_12h"]
But this will only work based on some assumptions I've made about the JSON you've provided. Is there a way you can link to a paste of the full JSON file?
Create two simple structs to hold your data (I didn't add all fields here)
struct PriceInfo {
let avg12h: String
let avg1h: String
let rates: [Rate]
}
struct Rate {
let last: String
}
then after converting json you can map it to a dictionary of [String: PriceInfo] where the key is the currency code
do {
if let json = try JSONSerialization.jsonObject(with: content) as? [String: Any] {
let prices: [String: PriceInfo] = json.mapValues {
let dict = $0 as? [String: Any]
let avg12h = dict?["avg_12h"] as? String ?? ""
let avg1h = dict?["avg_1h"] as? String ?? ""
let rates = dict?["rates"] as? [String: String] ?? [:]
return PriceInfo(avg12h: avg12h, avg1h: avg1h, rates: rates.compactMap { rate in Rate(last: rate.value) } )
}
}
} catch {
print(error)
return
}
Try to use CodingKey, it will be more clearer and JSONDecoder().decode method. I assume that you use any JsonViewer

Convert JSON encoded class to dictionary for Alamofire parameters

Let's say I got the following struct
public class Response: Codable {
let status: String
let code: String
let id: String
}
What I want is to get the class properties and values as [String: Any] to send it through Alamofire like this:
let response: Response = Response(status: "A", code: "B", uuid: "C")
let data = try JSONEncoder().encode(res)
//Data to [String : Any]
Alamofire.request("endpoint", method: .post, parameters: params).responseJSON {
// Handle response
}
You can use something like this:
let response: Response = Response(status: "A", code: "B", uuid: "C")
let data = try JSONEncoder().encode(res)
//Data to [String : Any]
do {
let params = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any]
Alamofire.request("endpoint", method: .post, parameters: params).responseJSON {
// Handle response
}
} catch {
print(error)
}
Try using JSONSerialization as below I had used to get data from JSON
func HitApi(){
Alamofire.request(urlToGetTimeTable, method: .get, parameters: nil , encoding:URLEncoding.default).responseJSON { (response) in
if(response.result.isSuccess)
{
if let JSON = response.result.value
{
print("JSON: \(JSON)")
do {
//Clearing values in Array
self.subjectNameArray.removeAll()
//get data and serialise here to get [String:Any]
if let data = response.data,
let json = try JSONSerialization.jsonObject(with: data) as? [String: Any],
let dataDict = json["data"] as? [[String: Any]]
{
// iterate an array
for dict in dataDict
{
//get data from JSON Response
let subjectName = dict["subjects_id"] as? String
self.subjectNameArray.append(subjectName!)
}
// TableView Delegate & DataSource
// Reload TableView
self.tableView.dataSource = self;
self.tableView.delegate = self;
self.tableView.reloadData()
}
}
catch
{
//Error case
print("Error deserializing JSON: \(error)")
}
}
}
if(response.result.isFailure)
{
//Show Alert here //reason Failure
}
}
}
Give you an idea to get response as [String:Any] using son serialisation , you can use Above format in Post Method need some Modification. I deleted Rest code and showed main Code that was required

Turning JSON file into an array in Swift/Xcode

I have been struggling all week. I am new to programming. I cannot turn a simple JSON file into a dictionary in Xcode. There is little simplified documentation online using the new method of Codable. So I am using a walkthrough, which has the following code.
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
guard let path = Bundle.main.path(forResource: "menu", ofType: "json") else { return }
let url = URL(fileURLWithPath: path)
do {
let data = try Data(contentsOf: url)
let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers)
//print(json)
guard let array = json as? [Any] else { return }
for user in array {
guard let userDict = user as? [String: Any] else { return }
guard let drinks = userDict["drinks"] as? String else { print("not a String"); return }
guard let junkFood = userDict["junk-food"] as? String else { return }
print(drinks)
print(junkFood)
print(" ")
}
}
catch {
print(error)
}
}
}
The below code is what my JSON looks like.
{"menu": {
"drinks": [
{"coke": "20"},
{"pepsi": "20"},
{"water": "20"}
],
"junk-food": [
{"hamburger": "40"},
{"fries": "20"},
{"pizza": "20"}
]
}}
Can anyone please walk me through, or show me some simplified documentation as to how I can turn the JSON into a dictionary that I can later map the data from? I am using Xcode and trying to work out Swift 4.
Thanks in advance for your patience.
My guess is that your json is actually a Dictionary not an Array. So guard let array = json as? [Any] else { return } is falling through because the json is [String: Any]. You can get to the array with the "menu" key.
Here's an updated version of your code:
do {
let data = try Data(contentsOf: url)
let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers)
//print(json)
guard let menuDict = json as? [String: Any] else { return }
guard let drinks = menuDict["drinks"] as? [[String: Any]] else {
print("not an array of dictionaries")
return
}
guard let junkFood = menuDict["junk-food"] as? [[String: Any]] else {
print("not an array of dictionaries")
return
}
print(drinks)
print(junkFood)
print(" ")
}
Try that, let me know if it works. This is just the do block by the way.

Struct Init with JSON and flatMap

I'm having a problem with the following code. I'm downloading a list of actors in JSON and I want to populate Struct Actor with the received data. Everything works great until I try to flatMap on the received data and try to initialize the struct Actor. When I try to compile the code i get the error: Cannot assign value of type '()' to type [Actor]. The error corresponds to a line in viewDidLoad actorsList = downloadActors() Would anybody have any recommendation who to solve this?
import UIKit
func downloadActors() {
var request = URLRequest(url: URL(string: "url...")!)
request.httpMethod = "POST"
let postString = "actorGroup=\("Superhero")"
request.httpBody = postString.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
DispatchQueue.main.async {
guard let data = data, error == nil else {
print("error=\(error)")
return
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 {
print("error : statusCode should be 200 but is \(httpStatus.statusCode)")
print("response = \(response)")
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode == 200 {
do {
let json = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers) as? [String: AnyObject]
guard let actorsJSON = json?["response"] as? [[String : AnyObject]] else {
return
}
} catch {
print("catch error")
}
}
}
}
task.resume()
}
func loadActors() -> [Actor] {
if let actors = actorsJSON as? [[String : AnyObject]] {
return actors.flatMap(Actor.init)
}
}
let actorsArray = loadActors()
class MasterViewController: UITableViewController {
var actorsList = [Actor]()
var detailViewController: DetailViewController? = nil
var objects = [Any]()
override func viewDidLoad() {
super.viewDidLoad()
actorsList = downloadActors()
print(actorsList)
Struct Actors is as follows:
struct Job {
let actorGroup: String
let actorName: String
}
extension Actor: JSONDecodable {
init?(JSON: [String : AnyObject]) {
guard let actorGroup = JSON["actorGroup"] as? String, let actorName = JSON["actorName"] as? String else {
return nil
}
self. actorGroup = actorGroup
self. actorName = actorName
}
}
let listActors = actorsJSON as? [[String : AnyObject]] {
Should be:
if let listActors = actorsJSON as? [[String : AnyObject]] {
Edit: For more info I'd like to add Vadian's comment:
Very confusing code. What does the function in the middle of the do block? Why do you type-check actorsJSON twice? The computed property is let listActors... which should be probably an optional binding (if let ... ). Further .mutableContainers is completely nonsense in Swift. And finally a JSON dictionary is [String:Any] in Swift 3.