Parsing data asynchronously using URLSession and SwiftyJSON not working - ios

I'm trying to parse data using SwiftyJSON but for some reason I keep getting runtime errors. I left the errors in the code with //, there are 2 of them. This is a sample of the data I'm trying to parse:
result = {
cost = {
"airbnb_median" = {
CHF = 86;
USD = 86;
}
}
This is the function I use to parse the JSON after it has been retrieved by URLSession:
func parseJSONFromData(_ jsonData: Data?) -> JSON?
{
if let data = jsonData {
do {
let jsonDictionary = try JSON(data)
return jsonDictionary
} catch let error as NSError {
print("error processing json data: \(error.localizedDescription)")
}
}
return nil
}
This is my custom JSON object:
class JSONObject {
var airbnbUS: Int
var airbnbLocal: Int
init(airbnbUS: Int, airbnbLocal: Int)
init(resultsDictionary:[String: Any]){
airbnbUS = (resultsDictionary["cost"]["airbnb_median"]["USD"] as? Int)!///Error Type 'Any?' has no subscript members
airbnbLocal = (resultsDictionary["cost"]["airbnb_median"]["CHF"] as? Int)!
}
This is how I update the dictionary for the JSOn object values:
static func updateResultsDictionary(urlExtension: String, completion:
#escaping (JSONObject?) -> Void) {
let nm = NetworkManager.sharedManager
_ = nm.getJSONData(urlExtension: urlExtension) {data in
guard let jsonDictionary = nm.parseJSONFromData(data),
let resultDictionaries = jsonDictionary["result"] //Error Initializer for conditional binding must have Optional type, not 'JSON'
else {
completion(nil)
return
}
for resultsDictionary in resultDictionaries {
let jsonInfo = JSONObject(resultsDictionary: resultsDictionary)
completion(jsonInfo)
}
}
}

Related

ios swift json data to dictionary

I have following format of data from a php script
{"Response":"OK","Data":{"ID":"1","data1":"sample1"}}
I need to extract data1 using http in swift, below is the code, but the code is not working as expected, that is if let data = dict?["Data"] as? [[String:Any]] { not giving any return.
fetchDataFromDB { (dict, error) in
if(dict?["Response"] as! String == "OK"){
var dd = dict?["Data"] as? [String:Any]
if let data = dict?["Data"] as? [[String:Any]] {
for d in data {
if let data1 = d["data1"] as? String {
self.data1 = data1
}
}
}
}
The function fetchDataFromDB
func fetchDataFromDB(completion: #escaping ([String:Any]?, Error?) -> Void) {
let url = URL(string: "http://test.com/get__details.php")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
do {
if let array = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String:Any]{
completion(array, nil)
}
} catch {
print(error)
completion(nil, error)
}
}
task.resume()
}
The (unused) line above is correct, it's a dictionary. And handle the error
fetchDataFromDB { (dict, error) in
if let error = error { print(error); return }
if let dict?["Response"] as? String == "OK",
let data = dict?["Data"] as? [String:Any],
let data1 = data["data1"] as? String {
self.data1 = data1
}
}
And as the expected type of the root object is a dictionary name it accordingly
if let dictionary = try JSONSerialization...
Consider to use Codable to parse the JSON
Best practice is to use Codable considering the json example that you gave your class should look like:
import Foundation
// MARK: - ResponseDB
struct ResponseDB: Codable {
let response: String
let data: DataClass
enum CodingKeys: String, CodingKey {
case response = "Response"
case data = "Data"
}
}
// MARK: - DataClass
struct DataClass: Codable {
let id, data1: String
enum CodingKeys: String, CodingKey {
case id = "ID"
case data1
}
}
In order to decode the response to your class add this code to your class or a custom class:
// MARK: - Helper functions for creating encoders and decoders
func newJSONDecoder() -> JSONDecoder {
let decoder = JSONDecoder()
if #available(iOS 10.0, OSX 10.12, tvOS 10.0, watchOS 3.0, *) {
decoder.dateDecodingStrategy = .iso8601
}
return decoder
}
func newJSONEncoder() -> JSONEncoder {
let encoder = JSONEncoder()
if #available(iOS 10.0, OSX 10.12, tvOS 10.0, watchOS 3.0, *) {
encoder.dateEncodingStrategy = .iso8601
}
return encoder
}
// MARK: - URLSession response handlers
extension URLSession {
fileprivate func codableTask<T: Codable>(with url: URL, completionHandler: #escaping (T?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
return self.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else {
completionHandler(nil, response, error)
return
}
completionHandler(try? newJSONDecoder().decode(T.self, from: data), response, nil)
}
}
func responseDBTask(with url: URL, completionHandler: #escaping (ResponseDB?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
return self.codableTask(with: url, completionHandler: completionHandler)
}
}
To read value from url:
let task = URLSession.shared.responseDBTask(with: YOUR_URL_HERE) { responseDB, response, error in
if let responseDB = responseDB {
if responseDB.response == "OK" {
self.data1 = responseDB.data.data1
}
}
}
task.resume()

need to get the country name from open api

Needs to get country name from below api call :
https://restcountries.eu/rest/v1/all
My code :
var arrRes = []
func getCountry() {
let Url: String = "https://restcountries.eu/rest/v1/all"
Alamofire.request(Url).responseJSON { (responseData) -> Void in
do {
if let datas = responseData.result.value {
let data = (datas as AnyObject).data(using: .utf8)!
let parseData = try JSONSerialization.jsonObject(with: data, options: [])
for country in parseData {
if let name = country["name"] as? String {
print(name)
}
}
}
}
catch let error as NSError {
print(error)
}
}
}
getting error here : 'Any' is not convertible to 'AnyObject' on below line let data = (datas as AnyObject).data(using: .utf8)!..
I need to get only name and append to my array.Any other idea or solution to achieve that ?
Replace do catch block of statement with this.
do {
if let countries = responseData.result.value as? [[String: Any]] {
for country in countries {
if let name = country["name"] as? String {
print(name)
}
}
}
}
catch let error as NSError {
print(error)
}
Try this, its working fine for me.
let urlStr = "https://restcountries.eu/rest/v1/all"
let setFinalURl = urlStr.addingPercentEncoding (withAllowedCharacters: .urlQueryAllowed)!
var request = URLRequest(url: URL(string: setFinalURl)!)
request.httpMethod = HTTPMethod.get.rawValue
Alamofire.request(request).responseJSON
{ (responseObject) -> Void in
if responseObject.result.isSuccess
{
print(responseObject.result.value!)
if "\(String(describing: responseObject.response!.statusCode))" == "200"
{
let result = responseObject.result.value! as AnyObject
let countryNamesArr = result.value(forKey: "name") as! NSArray
print(countryNamesArr)
}
else
{
// handle error
}
}
if responseObject.result.isFailure
{
let error : Error = responseObject.result.error!
print(error.localizedDescription)
}
}
You can try
struct Root: Codable {
let name: String
}
func getCountry() {
let urlStr = "https://restcountries.eu/rest/v1/all"
Alamofire.request(urlStr).responseData { (data) in
do {
guard let data = data.data else { return }
let res = try JSONDecoder().decode([Root].self,from:data)
print(res)
}
catch {
print(error)
}
}
}
Just remove this line
let data = (datas as AnyObject).data(using: .utf8)!
and in optional binding just assign data, since value is of type Data?, from optional binding you get Data
if let data = responseData.result.value
then don't forget to downcast your json to array [String:Any]
...jsonObject(with: data, options: []) as? [[String:Any]]
... then don't forget to unwrap this array or you wouldn't be able to iterate through it in for each loop
Also note that since there is Codable, you should use it instead of JSONSerialization. Then you can decode your json using JSONDecoder to your own model which conforms to protocol Decodable.
As a simple approach, you could implement getCountry() like this:
func getCountry() {
let url: String = "https://restcountries.eu/rest/v1/all"
Alamofire.request(url).responseJSON { response in
if let resultValue = response.result.value, let countryObjects = resultValue as? [[String: Any]] {
let countryNames = countryObjects.compactMap { $0["name"] as? String }
print(countryNames)
}
}
}
At this point, there is no need to use JSONSerialization to get the country names; According to the API response, responseData.result.value is an array of countries (dictionaries), each dictionary has a "name" value, what you should do is to map the response to an array of string. countryNames should contains what are you looking for.
The benefit of using compactMap is to avoid any nil name, so countryNames should be [String] instead of [String?].
However, if you believe that you would need to transform the whole response objects into a custom objects (instead of dictionaries), I would highly recommend to follow the approach of using Decodable.
My code, its working well for me.
Swift 5
public func getCountry(completion: #escaping ([String]) -> ()) {
let url: String = "https://restcountries.eu/rest/v1/all"
AF.request(url).responseJSON { (responseData) -> Void in
do {
guard let data = responseData.data else { return }
let res = try JSONDecoder().decode([CountryName].self,from:data)
completion(self.getCountryName(countryName: res))
}
catch {
print(error)
}
}
}
struct CountryName: Codable {
let name: String
}
private func getCountryName(countryName:[CountryName]) -> [String]{
var country:[String] = []
for index in 0...countryName.count - 1{
country.append(countryName[index].name)
}
return country
}

Get JSON data from NSDictionary

I have struct in my api
{
"Hall":"Hall",
"Date":20180501,
"Prices":[
{
"Time":1,
"Price":4000
},
{
"Time":2,
"Price":4000
},
{
"Time":3,
"Price":4000
}
]
}
Now I'm stuck and can't pull out the price and time. I understand that there were many question, but I still can't understand, please help.
I use this code:
let url = URL(string: "http://<...>/api/prices?hall=<...>&date=20180501")
var request = URLRequest(url: url!)
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) in
if let err = error {
print(err)
} else {
do {
if let jsonResult = try JSONSerialization.jsonObject(with: data!, options: []) as? NSDictionary {
// ...
}
DispatchQueue.main.async {
self.collectionView.reloadData()
}
} catch {
print("error")
}
}
})
task.resume()
}
I'm new with json, and just started learning it. I know it's easy, but I can't figure it out. I also know that i can use codable and decodable, but now I need to get price and time in this implementation.
First of all don't use NSArray / NSDictionary, use native Swift types.
The value for key Prices is an array of [String:Int] dictionaries:
if let jsonResult = try JSONSerialization.jsonObject(with: data!) as? [String:Any],
let prices = jsonResult["Prices"] as? [[String:Int]] {
for price in prices {
print(price["Time"]!, price["Price"]!)
}
}
However I would recommended to decode the JSON into a struct which is very simple in Swift 4
struct Item : Decodable {
let hall : String
let date : Int
let prices : [Price]
private enum CodingKeys : String, CodingKey { case hall = "Hall", date = "Date", prices = "Prices"}
}
struct Price : Decodable {
let time, price : Int
private enum CodingKeys : String, CodingKey { case time = "Time", price = "Price"}
}
do {
let result = try JSONDecoder().decode(Item.self, from: data!)
print(result)
} catch { print(error) }
Product Structure:
struct Product{
let time:Int
let price:Int
init(_ object:[String:Int]){
self.time = object["Time"] ?? 0
self.price = object["Price"] ?? 0
}
}
Class Variable:
var products = [Product]()
JSON parsing:
do{
if let jsonObject = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [String:Any] {
//Use Swift dictionary instead of NSDictionary
if let prices = jsonObject["Prices"] as? [[String:Int]]{
for price in prices{
self.products.append(Product(price))
}
}
}
}catch{
print("Error: ",error.localizedDescription)
}
Now, your products Array will contain all Price and Time
Example:
for product in products{
print("Time:",product.time)
print("Price:",product.price)
}
Output:
Time: 1
Price: 4000
Time: 2
Price: 4000
Time: 3
Price: 4000
Note: For better understanding, this is my video series about JSON parsing in swift 4
var timeArray = [String]()
var priceArray = [String]()
if jsonResult.object(forKey:"Prices") as? NSArray != nil
{
let pricesArray = jsonResult.object(forKey:"Prices") as! NSArray
for i in 0..<self.pricesArray.count
{
// Getting Time
if jsonResult.object(forKey:"Time") as? Int != nil
{
self.timeArray.append(jsonResult.object(forKey:"Time") as! Int)
}
else
{
print("Time is not a intrger")
}
// Getting Price
if jsonResult.object(forKey:"Price") as? Int != nil
{
self.priceArray.append(jsonResult.object(forKey:"Price") as! Int)
}
else
{
print("Price is not a intrger")
}
}
}
else
{
print("Empty Array")
}
if let json = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [String:Any], let pirces = json["prices"] // checking json is formed well.
{
for price in prices { // iterating throught array that you received.
print (price["Price"])
}
}

Deserialize a JSON array to a Swift array of objects

I am new to Swift, and am not able to figure out how to deserialize a JSON array to an array of Swift objects. I'm able to deserialize a single JSON user to a Swift user object fine, but just not sure how to do it with a JSON array of users.
Here is my User.swift class:
class User {
var id: Int
var firstName: String?
var lastName: String?
var email: String
var password: String?
init (){
id = 0
email = ""
}
init(user: NSDictionary) {
id = (user["id"] as? Int)!
email = (user["email"] as? String)!
if let firstName = user["first_name"] {
self.firstName = firstName as? String
}
if let lastName = user["last_name"] {
self.lastName = lastName as? String
}
if let password = user["password"] {
self.password = password as? String
}
}
}
Here's the class where I'm trying to deserialize the JSON:
//single user works.
Alamofire.request(.GET, muURL/user)
.responseJSON { response in
if let user = response.result.value {
var swiftUser = User(user: user as! NSDictionary)
}
}
//array of users -- not sure how to do it. Do I need to loop?
Alamofire.request(.GET, muURL/users)
.responseJSON { response in
if let users = response.result.value {
var swiftUsers = //how to get [swiftUsers]?
}
}
The best approach is the use Generic Response Object Serialization provided by Alamofire here is an example :
1) Add the extension in your API Manager or on a separate file
public protocol ResponseObjectSerializable {
init?(response: NSHTTPURLResponse, representation: AnyObject)
}
extension Request {
public func responseObject<T: ResponseObjectSerializable>(completionHandler: Response<T, NSError> -> Void) -> Self {
let responseSerializer = ResponseSerializer<T, NSError> { request, response, data, error in
guard error == nil else { return .Failure(error!) }
let JSONResponseSerializer = Request.JSONResponseSerializer(options: .AllowFragments)
let result = JSONResponseSerializer.serializeResponse(request, response, data, error)
switch result {
case .Success(let value):
if let
response = response,
responseObject = T(response: response, representation: value)
{
return .Success(responseObject)
} else {
let failureReason = "JSON could not be serialized into response object: \(value)"
let error = Error.errorWithCode(.JSONSerializationFailed, failureReason: failureReason)
return .Failure(error)
}
case .Failure(let error):
return .Failure(error)
}
}
return response(responseSerializer: responseSerializer, completionHandler: completionHandler)
}
}
public protocol ResponseCollectionSerializable {
static func collection(response response: NSHTTPURLResponse, representation: AnyObject) -> [Self]
}
extension Alamofire.Request {
public func responseCollection<T: ResponseCollectionSerializable>(completionHandler: Response<[T], NSError> -> Void) -> Self {
let responseSerializer = ResponseSerializer<[T], NSError> { request, response, data, error in
guard error == nil else { return .Failure(error!) }
let JSONSerializer = Request.JSONResponseSerializer(options: .AllowFragments)
let result = JSONSerializer.serializeResponse(request, response, data, error)
switch result {
case .Success(let value):
if let response = response {
return .Success(T.collection(response: response, representation: value))
} else {
let failureReason = "Response collection could not be serialized due to nil response"
let error = Error.errorWithCode(.JSONSerializationFailed, failureReason: failureReason)
return .Failure(error)
}
case .Failure(let error):
return .Failure(error)
}
}
return response(responseSerializer: responseSerializer, completionHandler: completionHandler)
}
}
2) update your model object like this:
final class User: ResponseObjectSerializable, ResponseCollectionSerializable {
let username: String
let name: String
init?(response: NSHTTPURLResponse, representation: AnyObject) {
self.username = response.URL!.lastPathComponent!
self.name = representation.valueForKeyPath("name") as! String
}
static func collection(response response: NSHTTPURLResponse, representation: AnyObject) -> [User] {
var users: [User] = []
if let representation = representation as? [[String: AnyObject]] {
for userRepresentation in representation {
if let user = User(response: response, representation: userRepresentation) {
users.append(user)
}
}
}
return users
}
}
3) then you can use it like that :
Alamofire.request(.GET, "http://example.com/users")
.responseCollection { (response: Response<[User], NSError>) in
debugPrint(response)
}
Source: Generic Response Object Serialization
Useful Link: Alamofire JSON Serialization of Objects and Collections
Since you are using Alamofire to make your requests why don't you give a chance to Hearst-DD ObjectMapper it has an Alamofire extension AlamofireObjectMapper. I think it'll save you time!
I would loop through them then add each user to an array (preferably a property of the VC and not an instance variable) but here is an example.
Alamofire.request(.GET, "YourURL/users")
.responseJSON { response in
if let users = response.result.value {
for user in users {
var swiftUser = User(user: user as! NSDictionary)
//should ideally be a property of the VC
var userArray : [User]
userArray.append(swiftUser)
}
}
}
You could also try EVReflection https://github.com/evermeer/EVReflection
It's even more simple, i.e. to parse JSON (code snippet taken from EVReflection link):
let json:String = "{
\"id\": 24,
\"name\": \"Bob Jefferson\",
\"friends\": [{
\"id\": 29,
\"name\":
\"Jen Jackson\"}]}"
you can use this class:
class User: EVObject {
var id: Int = 0
var name: String = ""
var friends: [User]? = []
}
in this way:
let user = User(json: json)

How to set result as array in BFTask of Bolts iOS framework?

I've a method where I want to return array of structs on success of a Alamofire callback.
func getPopularMedias() -> BFTask {
let instaUrl: String = "https://api.instagram.com/v1/media/popular"
let user: PFUser = currentUser()
let accessToken = user.objectForKey("accessToken") as! String
var medias: [Media] = []
let task = BFTaskCompletionSource()
Alamofire.request(.GET, instaUrl, parameters: ["access_token": accessToken])
.responseJSON { request, response, data in
var json = JSON(data.value!)
for(_, subJson): (String, JSON) in json["data"] {
let image: UIImage = NSURL(string: subJson["images"]["low_resolution"]["url"].stringValue)
.flatMap { NSData(contentsOfURL: $0) }
.flatMap { UIImage(data: $0) }!
let profileImage: UIImage = NSURL(string: subJson["user"]["profile_picture"].stringValue)
.flatMap { NSData(contentsOfURL: $0) }
.flatMap { UIImage(data: $0) }!
medias.append(Media(name: subJson["user"]["full_name"].stringValue, image: image, profileImage: profileImage))
}
task.setResult(medias)
}
return task.task
}
While setting task.setResult I'm getting an error called cannot convert value of [Media] to expected argument AnyObject!
Currently I'm running this on XCode 7.0 GM release and swift 2.
The issue here is that task.setResult takes AnyObject!, which requires classes, or an array of classes. Structs do not fall into this category (It would actually work with Any designator). You can either convert your Media struct to a class, or encapsulate it in a class.
Here`s one thing I would suggest you to do in that particular case. I see that you seem to be using SwiftyJSON, so what you could do is that you could pass json.rawData() as a result of a task and process it afterwards. Here is a real-life example.
I get some stuff from my database, get another set of data that is based on the initially acquired data and send it back to main function as json.rawData(). Afterwards, I am able to process my new data once I convert my NSData back to JSON.
p.s. You could also utilize a completionHandler, that will allow you to return whatever you want.
func startLoadingItunesDataFor(postsQuery: PFQuery, completionHandler: ((posts: [Post]) -> Void)) {
var newPosts = [Post]()
postsQuery.findObjectsInBackground().continueWithSuccessBlock({ (task: BFTask!) -> AnyObject! in
var tasks = [BFTask]()
if let result = task.result {
let posts = result as! [PFObject]
for post in posts {
let tempPost = Post(
//////
)
newPosts.append(tempPost)
tasks.append(self.getMovieInfoByITunesID(post["trackID"] as! Int))
}
}
return BFTask(forCompletionOfAllTasksWithResults: tasks)
}).continueWithSuccessBlock({ (task: BFTask!) -> AnyObject! in
let results = task.result as! NSArray
for (index, postData) in results.enumerate() {
let json = JSON(data: postData as! NSData)
////
}
completionHandler(posts: newPosts)
return nil
})
}
func getMovieInfoByITunesID(iTunesID: Int) -> BFTask {
let mainTask = BFTaskCompletionSource()
ITunesApi.lookup(iTunesID).request({ (responseString: String?, error: NSError?) -> Void in
if
// error == nil,
let responseString = responseString, let dataFromString = responseString.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false) {
let json = JSON(data: dataFromString)
do {
try mainTask.setResult(json["results"][0].rawData())
}
catch {
}
} else {
// process error
}
})
return mainTask.task
}

Resources