Swifty Json getting unknown but long way works fine? - json

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)

Related

Decoding JSON strings containing arrays

I'm encoding and decoding to and from JSON strings using JSONSerialization in the class below. I can encode both NSDictionaries & NSArrays and I can decode strings that have been encoded using NSDictionaries but not strings that were encoded from arrays, it barfs at JSONSerialization.jsonObject( ...
I can work without arrays, at a pinch but it would be nice to know why this is happening. Thoughts appreciated
let a = [1,2,3,4,5]
let s = JSON.Encode( a )!
JSON.Decode( s ) // fails
let a = ["a" : 1, "b" : 2, "c" : 3 ]
let s = JSON.Encode( a )!
JSON.Decode( s ) // works
-
class JSON: NSObject {
static func Encode( _ obj: Any ) -> String? {
do {
let data = try JSONSerialization.data(withJSONObject: obj, options:JSONSerialization.WritingOptions(rawValue: 0) )
if let string = NSString(data: data, encoding: String.Encoding.utf8.rawValue) {
return string as String
}
return nil
} catch let error as NSError {
return nil
}
}
static func Decode( _ s: String ) -> (NSDictionary?) {
let data = s.data(using: String.Encoding.utf8, allowLossyConversion: false)!
do {
// fails here to work on anything other than "{ a : b, c : d }"
// hates any [1,2,3] arrays in the string
let json = try JSONSerialization.jsonObject(with: data, options:JSONSerialization.ReadingOptions.mutableContainers) as! NSDictionary
return json
} catch let error as NSError {
return nil
}
}
}
You are casting the result to dictionary so it cannot work with something else.
A solution is to make the function generic and specify the expected return type.
I cleaned up the code a bit, basically it’s recommended to make a function throw if it contains a throwing API.
class JSON {
enum JSONError : Error { case typeMismatch }
static func encode( _ obj: Any ) throws -> String {
let data = try JSONSerialization.data(withJSONObject: obj)
return String(data: data, encoding: .utf8)!
}
static func decode<T>( _ s: String ) throws -> T {
let data = Data(s.utf8)
guard let result = try JSONSerialization.jsonObject(with: data) as? T else {
throw JSONError.typeMismatch
}
return result
}
}
let a = [1,2,3,4,5]
do {
let s = try JSON.encode(a)
let u : [Int] = try JSON.decode(s)
print(u)
} catch { print(error) }
Note (as always):
In Swift do not use NSDictionary, NSString and NSErrorand .mutableContainers is completely pointless.

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

Convert a callback Swift JSON AnyObject into a NSDictionary

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.

In Swift, changing in function doesn't work in outside

I have a tableview. cekilecek_data contains tableview datas. I get some datas from JSON and I want to append this datas to tableview. But I have to do this inside of jsonGetir(). However, it does not work. kodJSON and kodlarJSON are nil in viewDidLoad(). Also, cekilecek_data.append(kodJSON[1]) it doesn't add the datas to the table.
How do I fix it?
var cekilecek_data = ["Fenerbahçe", "Chelsea", "Arsenal"]
var kodlarJSON:String = ""
var kodJSON:[String] = []
func jsonGetir(){
let urls = NSURL(string: "http://gigayt.com/mackolik/deneme.php")
let sessions = NSURLSession.sharedSession().dataTaskWithURL(urls!){
data, response, error -> Void in
if (error != nil){ print(error) }
do {
if let jsonResult = try NSJSONSerialization.JSONObjectWithData(data!, options: []) as? NSDictionary {
kodlarJSON = jsonResult["kodlar"] as! String //101,102,103
kodJSON = kodlarJSON.componentsSeparatedByString(",")
cekilecek_data.append(kodJSON[1]) //Here doesn't work!
}
}
catch { print(error) }
}
sessions.resume()
}
Reload your tableview after you got data from server this way:
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}
And your final code will be:
func jsonGetir(){
let urls = NSURL(string: "http://gigayt.com/mackolik/deneme.php")
let sessions = NSURLSession.sharedSession().dataTaskWithURL(urls!){
data, response, error -> Void in
if (error != nil){ print(error) }
do {
if let jsonResult = try NSJSONSerialization.JSONObjectWithData(data!, options: []) as? NSDictionary {
self.kodlarJSON = jsonResult["kodlar"] as! String //101,102,103
self.kodJSON = self.kodlarJSON.componentsSeparatedByString(",")
self.cekilecek_data.append(self.kodJSON[1]) //Here doesn't work!
}
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData() //Reload tableview here.
}
}
catch { print(error) }
}
sessions.resume()
}
With kodJSON = kodlarJson.componentsseparatedByString(",") you are creating an Array of only one object. Then with cekilecke_data.append(kodJSON[1]) you are trying to append your data with the second object from this array that you have only set one object to.
do {
if let jsonResult = try NSJSONSerialization.JSONObjectWithData(data!, options: []) as? NSDictionary {
kodlarJSON = jsonResult["kodlar"] as! String
kodJSON = kodlarJSON.componentsSeparatedByString(",")
// this line sets kodJSON:[String] to ["kodlar"]
cekilecek_data.append(kodJSON[1]) //Here doesn't work!
// this tries to append cekilecek_data from index [1] or second slot of kodJSON which only has one entry
}
}
if you change line to kodlarJSON = jsonResult["kod,lar"] as! String, it would work because kodJSON[1] would equal "lar"

Asynchronous Issue JSON Swift

let task = session.dataTaskWithURL(url!, completionHandler: {
data, response, error -> Void in
if (error != nil) {
println(error)
} else {
let jsonresult = NSJSONSerialization.JSONObjectWithData(data, options:NSJSONReadingOptions.MutableContainers, error: nil) as NSDictionary
var dummyfeed:AnyObject
//println(jsonresult)
for var i = 0; i < jsonresult["feed"]!.count; i++ {
self.feeds.append([String:String]())
dummyfeed = jsonresult["feed"]![i] as NSDictionary
self.feeds[i]["id"] = dummyfeed["id"] as? String
self.feeds[i]["name"] = dummyfeed["name"] as? String
self.feeds[i]["status"] = dummyfeed["status"] as? String
self.feeds[i]["profilePic"] = dummyfeed["profilePic"] as? String
self.feeds[i]["timeStamp"] = dummyfeed["timeStamp"] as? String
self.feeds[i]["url"] = dummyfeed["url"] as? String
}
}
})
task.resume()
So Feeds is a global variable, so that I display the picture of each entry in Feeds on a table view. But it's calling asynchronously println(self.feeds) inside the task variable and println(feeds) outside of the task variable are differnent. How do I make it synchronously?
Do not make it run synchronously. Run it asynchronously, but then synchronize the interaction with feeds. The simplest way to achieve that it to dispatch the updating of the feeds back to the main queue and reloadData for the table view. This eliminates the possibility that you'll be using it from the main queue while it's mutating in the background, but avoids the horrible UX of doing this network request synchronously:
let task = session.dataTaskWithURL(url!) { data, response, error in
if (error != nil) {
println(error)
} else {
var parseError: NSError?
if let jsonresult = NSJSONSerialization.JSONObjectWithData(data, options:nil, error: nil) as? NSDictionary {
if let receivedFeeds = jsonresult["feed"] as? [[String: AnyObject]] {
dispatch_async(dispatch_get_main_queue()) {
self.feeds = [[String: String]]()
for receivedFeed in receivedFeeds {
var outputFeed = [String : String]()
outputFeed["id"] = receivedFeed["id"] as? String
outputFeed["name"] = receivedFeed["name"] as? String
outputFeed["status"] = receivedFeed["status"] as? String
outputFeed["profilePic"] = receivedFeed["profilePic"] as? String
outputFeed["timeStamp"] = receivedFeed["timeStamp"] as? String
outputFeed["url"] = receivedFeed["url"] as? String
self.feeds.append(outputFeed)
}
self.tableView.reloadData()
}
} else {
println("did not find `feed`")
}
} else {
println("problem parsing JSON: \(parseError)")
}
}
}
task.resume()
That should be a little more robust handling errors and employs asynchronous pattern of letting request run asynchronously, but dispatch updating of model object and UI back to the main thread.
let task = session.dataTaskWithURL(url!, completionHandler: {
data, response, error -> Void in
if (error != nil) {
println(error)
} else {
let jsonresult = NSJSONSerialization.JSONObjectWithData(data, options:NSJSONReadingOptions.MutableContainers, error: nil) as NSDictionary
var dummyfeed:AnyObject
//println(jsonresult)
for var i = 0; i < jsonresult["feed"]!.count; i++ {
self.feeds.append([String:String]())
dummyfeed = jsonresult["feed"]![i] as NSDictionary
dispatch_async(dispatch_get_main_queue()) {
self.feeds[i]["id"] = dummyfeed["id"] as? String
self.feeds[i]["name"] = dummyfeed["name"] as? String
self.feeds[i]["status"] = dummyfeed["status"] as? String
self.feeds[i]["profilePic"] = dummyfeed["profilePic"] as? String
self.feeds[i]["timeStamp"] = dummyfeed["timeStamp"] as? String
self.feeds[i]["url"] = dummyfeed["url"] as? String
}
}
self.tableView.reloadData()
}
})
task.resume()
Hey Rob, I did what I think you tell me to do, and feeds is still empty :(
I have same problem my code is was working fine, but now, using dataTaskWithURL it didn't return any data, or even error. I think issue is iOS 8.2 I upgraded.