Search code examples
jsonswiftnsdictionaryanyobject

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",
    }
  ]
}

Solution

  • 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.