Convert a callback Swift JSON AnyObject into a NSDictionary - json

I have a network connection with reads the data using JSON and gives a callback;
executeRequestURL(requestURL: url, taskCallback: {(status, resp) -> Void in
if (status == true) {
if let results = resp as? NSDictionary {
print ("\(results.count) results found")
let list = results.allValues.first as! NSArray
print (list)
}
} else {
print ("Error -- \(resp)")
}
})
This calls;
private class func executeRequestURL(requestURL: NSURL, taskCallback: #escaping (Bool, AnyObject?) -> ()) {
print ("Attempting URL -- \(requestURL)")
let request: NSURLRequest = NSURLRequest(url: requestURL as URL, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: kAPI_TIMEOUT)
let session: URLSession = URLSession.shared
let task = session.dataTask(with: request as URLRequest, completionHandler: {
(data, response, error) in
guard error == nil else {
print(error)
return
}
guard let data = data else {
print("Data is empty")
return
}
let json = try! JSONSerialization.jsonObject(with: data, options: [])
//print(json)
if let response = response as? HTTPURLResponse , 200...299 ~= response.statusCode {
taskCallback(true, json as AnyObject?)
} else {
taskCallback(false, json as AnyObject?)
}
})
task.resume()
}
The problem I have is that I want to read the results into a dictionary, loop through it and create objects.
For now, I will put my code in the executeRequestURL just to ensure it works, but I intend to seperate this code away for the required entity.
Question:
How do I read the resp as a dictionary?
Thanks
Sample response follows;
{
"objects": [
{
"uid": "coll_20ce39424470457c925f823fc150b3d4",
"title": "Popular",
"temp_image": "",
"body": "",
"active": true,
"slug": "popular",
"created": "2014-10-25T12:45:54+00:00",
"modified": "2014-10-25T12:45:54.159000+00:00",
"ends_on": "2100-01-01T00:00:00+00:00",
}
]
}

As the JSON is a dictionary, return a dictionary ([String:Any]) from the callback. In Swift 3 AnyObject has become Any. The strong type system of Swift encourages to be always as specific as possible.
Do a better error handling! You should return an error rather than just false.
The code uses the new Swift 3 structs URL and URLRequest
private class func executeRequestURL(requestURL: URL, taskCallback: #escaping (Bool, [String:Any]?) -> ()) {
print ("Attempting URL -- \(requestURL)")
let request = URLRequest(url: requestURL, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: kAPI_TIMEOUT)
let session = URLSession.shared
let task = session.dataTask(with: request, completionHandler: {
(data, response, error) in
guard error == nil else {
print(error)
taskCallback(false, nil)
return
}
guard let data = data else {
print("Data is empty") // <- this will never be reached. If there is no error,
taskCallback(false, nil) // data is always non-nil.
return
}
if let response = response as? HTTPURLResponse , 200...299 ~= response.statusCode {
let json = try! JSONSerialization.jsonObject(with: data, options: []) as! [String:Any]
taskCallback(true, json)
} else {
taskCallback(false, nil)
}
})
task.resume()
}
The JSON result contains a dictionary with one key objects which contains an array of dictionaries. JSON collection types are very easy to distinguish: {} is dictionary, [] is array.
To map the JSON to objects create a struct
struct Item {
var uid : String
var title : String
var tempImage : String
var body : String
var active : Bool
var slug : String
var created : String
var modified : String
var endOn : String
}
and an array
var items = [Item]()
Then map the dictionaries to Item
if let objects = json["objects"] as? [[String:Any]] {
for object in objects {
let uid = object["uid"] as! String
var title = object["title"] as! String
var tempImage = object["temp_image"] as! String
var body = object["body"] as! String
var active = object["active"] as! Bool
var slug = object["slug"] as! String
var created = object["created"] as! String
var modified = object["modified"] as! String
var endOn = object["end_on"] as! String
let item = Item(uid: uid, title: title, tempImage:tempImage, body: body, active: active, slug: slug, created: created, modified: modified, endOn: endOn)
items.append(item)
}
The JSON values seem to come from a database which includes always all fields so the forced unwrapped values are safe.

I've done it like so:
func getHttpData(urlAddress : String)
{
// Asynchronous Http call to your api url, using NSURLSession:
guard let url = URL(string: urlAddress) else
{
print("Url conversion issue.")
return
}
URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) -> Void in
// Check if data was received successfully
if error == nil && data != nil {
do {
// Convert NSData to Dictionary where keys are of type String, and values are of any type
let json = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.mutableContainers) as! [String:AnyObject]
// Call whatever function you want to do with your dictionary
useMyDictionary(dictionary: json)
} catch {
print(error)
// Something went wrong
}
}
else if error != nil
{
print(error)
}
}).resume()
}

There are many other ways but I like to do it using ObjectMapper. it looks cleaner to me. So just create a new Swift file, import ObjectMapper and write below code.
class yourDataModel: Mappable {
// MARK: - Constants & Variables
var myObjects: [yourDataModel]
required init?(_ map: Map) {
myObjects = []
}
func mapping(map: Map) {
myObjects <- map["objects"]
}
}
class YourCustomObjects: Mappable {
// MARK: - Constants & Variables
var userId:String
var title:String
var tempimage:String
var body:String
var active:Bool
var slug : String
var createdDate:String
var modifiedDate:String
var endDate:String
// MARK: - init
required init?(_ map: Map) {
userId = ""
title = ""
tempimage = ""
body = ""
active = false
slug = ""
createdDate = ""
modifiedDate = ""
endDate = ""
}
func mapping(map: Map) {
userId <- map["uid"]
title <- map["title"]
tempimage <- map["temp_image"]
body <- map["body"]
active <- map["active"]
slug <- map["slug"]
createdDate <- map["created"]
modifiedDate <- map["modified"]
endDate <- map["ends_on"]
}
}
Basically its your model class, now you just have to pass it your result in JSON which will be an AnyObject hopefully, and it will give you an array containing all your "objects" in it. You can use it like below
if let data = Mapper<yourDataModel>().map(resp){
print(data)
}
Try this, and let me know if you face any difficulty.

Related

How parse a json data that has a dictionary key:value pair? I'm trying to extract the symbol and value pair

How to correctly parse the following json data?
My problem is at the line: if let rates = data["rates"] as? NSDictionary...
The json data
{ "valid": true,
"timestamp": 1579683079,
"base": "USD",
"rates": {
"AED": 3.67316,
"AFN": 77.99911,
"ALL": 110.11741,
...
"ZAR": 14.45,
"ZMW": 14.63257
}
}
Code
import UIKit
struct CurrencyRate {
var valid: String
var timestamp: Int
var base: String
var rates: [String:Double] = [:]
}
class ViewController: UIViewController {
var mySymbols:[String] = []
var myValues:[Double] = []
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let jsonUrlString = "https://currencyapi.net/api/v1/rates?key=6b171cc58787d922eb53e3684d97784d165a&base=USD"
guard let url = URL(string: jsonUrlString) else { return }
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else { return }
//let dataAsString = String(data: data, encoding: .utf8)
if let rates = data["rates"] as? NSDictionary {
for (key, value) in rates {
self.mySymbols.append((key as? String)!)
self.myValues.append((value as? Double)!)
}
}
}.resume()
}
}
The syntax
for (key, value) in ...
to enumerate a dictionary exists only for native Swift dictionaries.
This is supposed to work, if you cast to specific [String:Double] you even get rid of the ugly type cast of key and value.
if let rates = data["rates"] as? [String:Double] {
for (key, value) in rates {
self.mySymbols.append(key)
self.myValues.append(value)
}
}
However you are encouraged to use the Decodable protocol to parse the JSON. There are only a few slightly changes.
struct CurrencyRate : Decodable {
let valid: Bool // must be Bool
let timestamp: Date
let base: String
let rates: [String:Double]
}
var rates = [String:Double]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let jsonUrlString = "https://currencyapi.net/api/v1/rates?key=6b171cc58787d922eb53e3684d97784d165a&base=USD"
guard let url = URL(string: jsonUrlString) else { return }
URLSession.shared.dataTask(with: url) { (data, _, error) in
guard let error = error else { print(error); return }
do {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .secondsSince1970
let result = try decoder.decode(CurrencyRate.self, from: data!)
self.rates = result.rates
print(rates)
} catch {
print(error)
}
}.resume()
}
timestamp is decoded as Date
Don't use NSDictionary - use Dictionary type in Swift. Also, you have data and it's not dictionary at all. It's Data type. Use should convert your data to your type. Try use this code:
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else { return }
let jsonResult = try? JSONSerialization.jsonObject(with: data)
if let dict = jsonResult as? Dictionary<String, Any>, let rates = dict["rates"] as? Dictionary<String, Double> {
for (key, value) in rates {
print(key, value)
self.mySymbols.append(key)
self.myValues.append(value)
}
}
}.resume()
There is a way with Codable, but I guess it will be the next challenge for you. Also, keep in mind that the code is executed asynchronously and the data in global variables will not appear immediately

Parsed JSON not updating on api call for a long time

For some reason the JSON object from parsing doesnt update after network calls to and api we built. I check the endpoint and now for a fact it updates right away. I have a timer being called every 10 sec to make the call but the parsed json doesnt update until after a minute or so. I have tried putting it on the main thread and that still doesnt work. Here is my code:
#objc func getLaunches() {
let simulator = UserDefaults.standard.string(forKey: self.launchSimulator)
if(simulator == self.password){
print("they are the same")
}
guard let launchUrl = URL(string: launchesURL) else {
return
}
let request = URLRequest(url: launchUrl)
DispatchQueue.main.async { [weak self] in
let task = URLSession.shared.dataTask(with: request, completionHandler: {
(data, response, error) -> Void in
if let error = error {
print(error)
return
}
// Parse JSON data
if let data = data {
self?.launches.removeAll()
self?.launches = (self!.parseJsonData(data: data))
let nextlaunch = self?.launches[0]
// Reload table view
self?.hours = nextlaunch?.time
self?.yearMonth = nextlaunch?.date
var fulltime = self?.yearMonth
fulltime!.insert("-", at: fulltime!.index(fulltime!.startIndex, offsetBy: 4))
fulltime!.insert("-", at: fulltime!.index(fulltime!.startIndex, offsetBy: 7))
fulltime = fulltime! + " "
fulltime = fulltime! + self!.hours
let fullFormatter = DateFormatter()
fullFormatter.dateFormat = "YYYY-MM-dd HH:mm"
fullFormatter.timeZone = TimeZone(abbreviation: "EST")
self?.launchDate = fullFormatter.date(from: fulltime!)
self?.getCountdown()
}
})
task.resume()
}
}
//parse launch info from json to dictionary into launches object
func parseJsonData(data: Data) -> [NextLaunch] {
var launches = [NextLaunch]()
do {
let jsonResult = try JSONSerialization.jsonObject(with: data, options:
JSONSerialization.ReadingOptions.allowFragments) as? NSDictionary
let jsonLaunches = jsonResult?["launches"] as! [NSDictionary]
for jsonLaunch in jsonLaunches {
let launch = NextLaunch()
launch.date = jsonLaunch["date"] as! String
launch.time = jsonLaunch["time"] as! String
if(launch.time == ""){
launch.time = "00:00"
}
launch.mission = jsonLaunch["mission"] as! String
launch.launchpad = jsonLaunch["launch_pad"] as! String
launch.image = jsonLaunch["image"] as! String
launch.delay = jsonLaunch["delayed"] as! String
//show delay image if it is delayed
if(launch.delay == "1"){
self.delayed()
}else{
self.notDelayed()
}
launches.append(launch)
}
} catch {
print(error)
}
return launches
}
You need
DispatchQueue.main.async {
self?.getCountdown()
}
As the response of URLSession.shared.dataTask(with: occurs in a background thread

Parsing JSON array without for statement

I want to find a solution for parsing JSON array. there is my code for parsing JSON but I want a solution without for statement I can parse array.
code for parsing:
func parsigJsonData(resultArray:any?){
if let resultArray = resultDic["trucks"] as? BaseModelData {
print(resultArray)
}
}
class BaseModel {
public typealias BaseModelData = (id:String?,title:String?,select:Bool)
var id : String?
var title : String?
var select : Bool = false
init(json: [String:Any]) {
self.id = json["id"] as? String
self.title = json["title"] as? String
self.select = false
}
}
extension BaseModel {
var tableRepresentation: [BaseModelData] {
return [(id:id,title:title,select:select)]
}
}
result array contains list of baseModel object. I try this code for parsing but that's not working and casting to BaseModelData unsuccessful.
if there is a solution for parsing JSON array without for statement?
Thank you for the solutions.
there is my json response sample:
{"trucks":[{"id":"1","title":"\u062e\u0627\u0648\u0631 \u062a\u0627 5 \u062a\u0646 \u0627\u062a\u0627\u0642 \u0686\u0648\u0628\u06cc"},{"id":"2","title":"\u062e\u0627\u0648\u0631 \u062a\u0627 5 \u062a\u0646 \u06a9\u0645\u067e\u0631\u0633\u06cc"},{"id":"3","title":"\u062e\u0627\u0648\u0631 \u062a\u0627 8 \u062a\u0646 \u0627\u062a\u0627\u0642 \u0686\u0648\u0628\u06cc"},{"id":"4","title":"\u062e\u0627\u0648\u0631 \u062a\u0627 8 \u062a\u0646 \u06a9\u0645\u067e\u0631\u0633\u06cc"},{"id":"5","title":"\u062e\u0627\u0648\u0631 \u06cc\u062e\u0686\u0627\u0644 \u062f\u0627\u0631"}]
Swift 4 allows you to make it very simple. Just add Decodable protocol,
struct BaseModel: Codable {
var id : String?
var title : String?
var select : Bool = false
}
And use this to retrieve data:
func makeRequest(completionHandler: #escaping ([BaseModel]?, Error?) -> Void) {
var urlRequest = URLRequest(url: url)
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request as URLRequest){
(data, response, error) in
guard let responseData = data else {
print("Error: did not receive data")
completionHandler(nil, error)
return
}
guard error == nil else {
completionHandler(nil, error)
return
}
let decoder = JSONDecoder()
do {
let dataObject = try decoder.decode([BaseModel].self, from: responseData)
completionHandler(dataObject, nil)
} catch {
print("error trying to convert data to JSON")
print(error)
completionHandler(nil, error)
}
}
task.resume()
}

swift json type data parsing after get data

I'm developing a function which is receiving some JSON after calling a NSURLRequest in Swift. I am having issues accessing the different data values with subscript after I have parsed the result.
The following is the retrieved JSON:
{"retCode":100,"retMsg":"Success","retData":{"usn":92,"id":"clipsys#gmail.com","nickname":"ppigimi","profile_image":"..\/upload\/profile\/20150528172839.jpeg","language":"jp","join_channel_nm":"korfolk","cert_key":"696D6FB453DC141E5295E9D8E37B8DD0F1AFC8E34CE30B74551ED74A447AC564","cert_flag":"Y","join_date":"20150518155650","token":"3ea0a5fec1b55a5a23b5f1dc5c14b040dcd71eea"}}
The following is my code. I don't know how to get the values usn and token inside of retData.
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
data, response, error in
if error != nil {
println("error=\(error)")
return
}
// You can print out response object
println("response = \(response)")
// Print out response body
let responseString = NSString(data: data, encoding: NSUTF8StringEncoding)
println("responseString = \(responseString)")
//Let's convert response sent from a server side script to a NSDictionary object:
var err: NSError?
var myJSON = NSJSONSerialization.JSONObjectWithData(data, options: .MutableLeaves, error:&err) as? NSDictionary
if let parseJSON = myJSON as? [String: AnyObject] {
// Now we can access value of First Name by its key
var retCode = parseJSON["retCode"] as? Int
println("retCode: \(retCode)")
if let retData = parseJSON["retData"] as? [AnyObject] {
for data in retData {
/**let usn = data["usn"]
println("USN = \(usn)")**/
}
}
if retCode == 100 {
//NSUserDefaults.standardUserDefaults().setValue("usn", forKey: String)
}
dispatch_async(dispatch_get_main_queue(), {
var myAlert = UIAlertController(title: "Alert", message: "AAAAAA", preferredStyle: UIAlertControllerStyle.Alert);
let okAction = UIAlertAction(title: "OK", style:UIAlertActionStyle.Default) {
action in
self.dismissViewControllerAnimated(true, completion: nil);
}
myAlert.addAction(okAction);
self.presentViewController(myAlert, animated: true, completion: nil);
});
}
}
task.resume()
The problem is that you are accessing retCode as [AnyObject]. It is not an Array, but actually a JSON object. You will need to cast it to a Dictionay, for example [String : AnyObject].
This means that your for loop where you are accessing the data can look something like the following:
if let retData = parseJSON["retData"] as? [String : AnyObject] {
for data in retData {
println("data is: \(data)")
/**let usn = data["usn"]
println("USN = \(usn)")**/
}
}

Swifty Json getting unknown but long way works fine?

I'm attempting to use SwiftyJson to pull some JSON data.
What's unusual is the "println(json)" says "unknowon" while if I pull the JSON data the regular way it works just fine -- the "println(pop)" says medium, as expected.
Below is the code I'm using. I started cutting out parts until I got to "println(json)" and then decided to try and handle it manually to see if it's SwiftyJson or me.
Any suggestions? I'm fairly new to iOS programming so I'm assuming I'm being silly in some form or another.
var ghostlandsJsonUrl: NSURL = NSURL(string: "http://us.battle.net/api/wow/realm/status?realm=Ghostlands")!
var jsonData: NSData!
var request: NSURLRequest = NSURLRequest(URL: ghostlandsJsonUrl)
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let task : NSURLSessionDataTask = session.dataTaskWithRequest(request, completionHandler: {(data, response, error) in
jsonData = data
if(jsonData != nil) {
let json = JSON(jsonData)
println(json)
} else {
println("jsonData: nil value... net down again?")
}
let jsonObject : AnyObject! = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil)
if let statuses = jsonObject as? NSDictionary{
if let realms = statuses["realms"] as? NSArray{
if let realm = realms[0] as? NSDictionary{
if let pop = realm["population"] as? NSString{
println(pop)
}
}
}
}
});
task.resume()
Looking at SwiftyJSON source code I can see that JSON is a simple struct. It implements the Printable protocol. Which give support to the print methods.
public var description: String {
if let string = self.rawString(options:.PrettyPrinted) {
return string
} else {
return "unknown"
}
}
Which means that for a reason or another the rawString method returns nil.
public func rawString(encoding: UInt = NSUTF8StringEncoding, options opt: NSJSONWritingOptions = .PrettyPrinted) -> String? {
switch self.type {
case .Array, .Dictionary:
if let data = self.rawData(options: opt) {
return NSString(data: data, encoding: encoding)
} else {
return nil
}
case .String:
return (self.object as String)
case .Number:
return (self.object as NSNumber).stringValue
case .Bool:
return (self.object as Bool).description
case .Null:
return "null"
default:
return nil
}
}
As you are fairly new to iOS development, I will tell you that the constructor doesn't expect a NSData object.
Here is the source:
public var object: AnyObject {
get {
return _object
}
set {
_object = newValue
switch newValue {
case let number as NSNumber:
if number.isBool {
_type = .Bool
} else {
_type = .Number
}
case let string as NSString:
_type = .String
case let null as NSNull:
_type = .Null
case let array as [AnyObject]:
_type = .Array
case let dictionary as [String : AnyObject]:
_type = .Dictionary
default:
_type = .Unknown
_object = NSNull()
_error = NSError(domain: ErrorDomain, code: ErrorUnsupportedType, userInfo: [NSLocalizedDescriptionKey: "It is a unsupported type"])
}
}
}
So you should pass it the unserialized NSData as it:
if let jsonData = data {
//jsonData can't be nil with this kind of if
let jsonObject : AnyObject! = NSJSONSerialization.JSONObjectWithData(jsonData, options: NSJSONReadingOptions.MutableContainers, error: nil)
let json = JSON(jsonObject)
println(json)
//...
The constructor of JSON does the serialisation. Below is the constructor code from SwiftyJSON git repo where you can directly pass the NSData.
public init(data:NSData, options opt: NSJSONReadingOptions = .AllowFragments, error: NSErrorPointer = nil) {
do {
let object: AnyObject = try NSJSONSerialization.JSONObjectWithData(data, options: opt)
self.init(object)
} catch let aError as NSError {
if error != nil {
error.memory = aError
}
self.init(NSNull())
}
}
In simple, you can directly use the data returned in the completion handler of NSURLSession data task as below in your code.
let json = JSON(data: jsonData)