Swift JSON parsing with Dictionary with Array of Dictionaries - json

I am a beginner in iOS development with Swift language. I have a JSON file contains the data as below.
{
"success": true,
"data": [
{
"type": 0,
"name": "Money Extension",
"bal": "72 $",
"Name": "LK_Mor",
"code": "LK_Mor",
"class": "0",
"withdraw": "300 $",
"initval": "1000 $"
},
{
},
{
},
]
}
I want to parse this file and have to return the dictionary which contain the data in the JSON file. This is the method I wrote.
enum JSONError: String, ErrorType {
case NoData = "ERROR: no data"
case ConversionFailed = "ERROR: conversion from JSON failed"
}
func jsonParserForDataUsage(urlForData:String)->NSDictionary{
var dicOfParsedData :NSDictionary!
print("json parser activated")
let urlPath = urlForData
let endpoint = NSURL(string: urlPath)
let request = NSMutableURLRequest(URL:endpoint!)
NSURLSession.sharedSession().dataTaskWithRequest(request) { (data, response, error) -> Void in
do {
guard let dat = data else {
throw JSONError.NoData
}
guard let dictionary: NSDictionary = try NSJSONSerialization.JSONObjectWithData(dat, options:.AllowFragments) as? NSDictionary else {
throw JSONError.ConversionFailed
}
print(dictionary)
dicOfParsedData = dictionary
} catch let error as JSONError {
print(error.rawValue)
} catch {
print(error)
}
}.resume()
return dicOfParsedData
}
When I modify this method to return a dictionary, it always return nil. How can I modify this method.

You can not return for an asynchronous task. You have to use a callback instead.
Add a callback like this one:
completion: (dictionary: NSDictionary) -> Void
to your parser method signature:
func jsonParserForDataUsage(urlForData: String, completion: (dictionary: NSDictionary) -> Void)
and call the completion where the data you want to "return" is available:
func jsonParserForDataUsage(urlForData: String, completion: (dictionary: NSDictionary) -> Void) {
print("json parser activated")
let urlPath = urlForData
guard let endpoint = NSURL(string: urlPath) else {
return
}
let request = NSMutableURLRequest(URL:endpoint)
NSURLSession.sharedSession().dataTaskWithRequest(request) { (data, response, error) -> Void in
do {
guard let dat = data else {
throw JSONError.NoData
}
guard let dictionary = try NSJSONSerialization.JSONObjectWithData(dat, options:.AllowFragments) as? NSDictionary else {
throw JSONError.ConversionFailed
}
completion(dictionary: dictionary)
} catch let error as JSONError {
print(error.rawValue)
} catch let error as NSError {
print(error.debugDescription)
}
}.resume()
}
Now you can use this method with a trailing closure to get the "returned" value:
jsonParserForDataUsage("http...") { (dictionary) in
print(dictionary)
}

Related

Custom parameters when API calling with Swift 5

I've got this function to fetch a JSON API, but I need to set custom parameters.
Here is the function:
func fetchTipsJson(completion: #escaping (Result<Root, Error>) -> ()) {
let urlString = "http://telemedapi_dev.assistcard.com/Api/Config/GetConfigGroupList"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, resp, err) in
// Error
if let err = err {
completion(.failure(err))
return
}
// Successful
do {
let tips = try JSONDecoder().decode(Root.self, from: data!)
completion(.success(tips))
print(tips)
} catch let jsonError {
completion(.failure(jsonError))
}
}.resume()
}
And here are the parameters:
{
"Parameters": {
"CountryCode": 540,
"ConfigurationPath": "TelemedGlobalConfig>Tips",
"LogApiCallsStatus": 2
},
"VisitorIp": null,
"ApplicationName": null,
"CurrentUICulture": "es-ES"
}
How can I do this? I can't find anything. Notice that I'm using the new Result from Swift 5, don't know if that changes anything.

Swift Searching For Values inside values of Dictionary

I have a URL which my app fetches. it prints a dictionary with two keys but inside one of the keys is a lot of information I would like to get for my app.
The URL gets lots of information but not as a conventional dictionary.
this is a VERY simplified version:
["person":
name: John
height: 187, "fruit": colour: red
]
etc...
so I would just want to get the name of the person inside the key person but I am having trouble finding this.
Is there any way to do this? I have been trying JSON Parsing, for loops and I am stuck.
Edit:
it isn't a dictionary inside a dictionary. If you would like to see what I am working with. Just copy and paste this link. It is an example of what I am using. http://itunes.apple.com/lookup?bundleId=com.burbn.instagram
I would need just the seller name or just the currency etc.
Code to read the link and print it:
override func viewDidLoad() {
super.viewDidLoad()
fetchData { (dict, error) in
print(dict!)
}
}
func fetchData(completion: #escaping ([String:Any]?, Error?) -> Void) {
let url = URL(string: link)!
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 data you are fetching is JSON. In order to use it, you will have to decode it. The recommended way is using JSONDecoder in Swift.
First you will have to define your model, which correspond to the data model, and make it conform to Codable protocol:
struct App: Codable {
var sellerName: String
// Alternatively, if you don't want to use an enum, you can use a String.
var currency: Currency
enum Currency: String, Codable {
case australianDollar = "AUD",
case britishPound = "GBP",
case euro = "EUR",
case hongKongDollar = "HKD",
case usDollar = "USD"
// Complete this with all the currency…
}
}
struct JSONResult: Codable {
var resultCount: Int
var results: [App]
}
Once this is done, you only have to edit your fetchData method so it returns an array App populated with the data you fetched.
Swift 4 version:
func fetchData(completion: #escaping (JSONResult?, Error?) -> Void) {
guard let url = URL(string: link) else { return }
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if let error = error {
completion(nil, error)
return
} else if let data = data {
do {
let decoder = JSONDecoder()
let result = try decoder.decode(JSONResult.self, from: data)
completion(result, nil)
} catch {
print(error)
completion(nil, error)
}
}
}
task.resume()
}
Swift 5 version using Result type:
func fetchData(completion: #escaping (Result<JSONResult, Error>) -> Void) {
guard let url = URL(string: link) else { return }
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if let error = error {
completion(.failure(error))
return
} else if let data = data {
do {
let decoder = JSONDecoder()
let result = try decoder.decode(JSONResult.self, from: data)
completion(.success(result))
} catch {
print(error)
completion(.failure(error))
}
}
}
task.resume()
}
More information about JSONDecoder
Dictionary data is:
let dict = ["person": ["name": "John", "height": "187"], "fruit": ["colour": "red"]]
Suppose you need name of the person. So you can do it by the following way.
if let person = dict["person"], let name = person["name"] as? String {
print (name)
}

-- function-call with completion URLRequest - JSON

i did read a lot about functions with completion-handler, but now i have a problem how to call this function (downloadJSON) in the correct way. Which parameters do i have to give in the function and handle the result-data (json) in my own class, where the function was called.
This is the code from David Tran. Hi makes wonderful tutorials, but in the code there is no call of this function.
let request: URLRequest
lazy var configuration: URLSessionConfiguration = URLSessionConfiguration.default
lazy var session: URLSession = URLSession(configuration: self.configuration)
typealias JSONHandler = (JSON?, HTTPURLResponse?, Error?) -> Void
func downloadJSON(completion: #escaping JSONHandler)
{
let dataTask = session.dataTask(with: self.request) { (data, response, error) in
// OFF THE MAIN THREAD
// Error: missing http response
guard let httpResponse = response as? HTTPURLResponse else {
let userInfo = [NSLocalizedDescriptionKey : NSLocalizedString("Missing HTTP Response", comment: "")]
let error = NSError(domain: DANetworkingErrorDomain, code: MissingHTTPResponseError, userInfo: userInfo)
completion(nil, nil, error as Error)
return
}
if data == nil {
if let error = error {
completion(nil, httpResponse, error)
}
} else {
switch httpResponse.statusCode {
case 200:
// OK parse JSON into Foundation objects (array, dictionary..)
do {
let json = try JSONSerialization.jsonObject(with: data!, options: []) as? [String : Any]
completion(json, httpResponse, nil)
} catch let error as NSError {
completion(nil, httpResponse, error)
}
default:
print("Received HTTP response code: \(httpResponse.statusCode) - was not handled in NetworkProcessing.swift")
}
}
}
dataTask.resume()
}
Let Xcode help you. Type downlo and press return. Xcode completes the function
Press return again and you get the parameters
You have to replace the placeholders with parameter names for example
downloadJSON { (json, response, error) in
if let error = error {
print(error)
} else if let json = json {
print(json)
}
}
Note:
There is a fatal type mismatch error in your code: The result of the JSONSerialization line is [String:Any] but the first parameter of the completion handler is JSON

Trouble parsing JSON

I am having trouble parsing some JSON in Swift. I am having trouble getting the errors variable it returns nil. I think it should be a dictionary?
Below is the JSON that is returned from my API as printed in the console.
{
error = "{\"name\":[\"The name has already been taken.\"],\"email\":[\"The email has already been taken.\"]}";
success = 0;
}
And here is the Swift code.
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
if let parseJSON = json {
print(parseJSON)
let success = parseJSON["success"] as? Int
if(success == 1) {
let myAlert = UIAlertController(title: "Alert", message: "Registration successful", preferredStyle: UIAlertControllerStyle.alert);
let okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.default){
(action) in
self.dismiss(animated: true, completion: nil)
}
myAlert.addAction(okAction);
self.present(myAlert, animated: true, completion: nil)
} else {
let errors = parseJSON["error"] as? NSDictionary
if(errors != nil){
print("NOT NIL")
// self.displayAlertMessage()
}
}
}
} catch{
print(error)
}
EDIT
Here is the JSON thats is printed using David's code below.
This is the parseJSON printed to the console.
["error": {"name":["The name has already been taken."],"email":["The email has already been taken."]}, "success": 0]
Here is my full method with Davids updated code.
let task = URLSession.shared.dataTask(with: request) { (theData: Data?, response: URLResponse?, theError: Error?) in
DispatchQueue.main.async
{
//spinningActivity!.hide(true)
if theError != nil {
self.displayAlertMessage(theError!.localizedDescription)
return
}
do {
guard let parseJSON = try JSONSerialization.jsonObject(with: theData!) as? [String:Any] else {return}
//print(parseJSON)
let success = parseJSON["success"] as? Int
if(success == 1) {
let myAlert = UIAlertController(title: "Alert", message: "Registration successful", preferredStyle: UIAlertControllerStyle.alert);
let okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.default){
(action) in
self.dismiss(animated: true, completion: nil)
}
myAlert.addAction(okAction);
self.present(myAlert, animated: true, completion: nil)
} else {
guard let errors = parseJSON["success"] as? Int else {return}
print(errors)
}
} catch{
print(error)
}
}
}
task.resume()
There are several issues with your code that might be not causing the issue directly, but are bad practices. Don't use NSDictionary in Swift, use [String:Any] when decoding JSON responses and don't use .mutableContainers as it has no effect in Swift, the mutability is determined by the let or var keyword when declaring the variable.
Moreover, don't include console print as the JSON response, include the actual JSON response in your question, as Swift's print statement doesn't produce a valid JSON.
let apiErrorResponse = """
{
"error": {
"name": "The name has already been taken.",
"email": ["The email has already been taken."]
},
"success": 0
}
"""
func handleApiErrorResponse(){
do {
guard let parseJSON = try JSONSerialization.jsonObject(with: apiErrorResponse.data(using: .utf8)!) as? [String:Any] else {return}
let success = parseJSON["success"] as? Int
if(success == 1) {
let myAlert = UIAlertController(title: "Alert", message: "Registration successful", preferredStyle: UIAlertControllerStyle.alert);
let okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.default){
(action) in
self.dismiss(animated: true, completion: nil)
}
myAlert.addAction(okAction);
self.present(myAlert, animated: true, completion: nil)
} else {
guard let errors = parseJSON["error"] as? [String:Any] else {return}
print(errors)
}
} catch{
print(error)
}
}
handleApiErrorResponse()
Output:
"["name": The name has already been taken., "email": <__NSSingleObjectArrayI 0x608000019f80>(\nThe email has already been taken.\n)\n]\n"

Swift passing method params Struct Decodable

,Swift 4 how can I pass Decodable Struct in method params and parse it in JSONDecoder()?
error:
Cannot invoke 'decode' with an argument list of type '(Decodable,
from: Data)'
struct JsonRespons: Codable {
let uid: String
let msisdn: String
let APK: String
let fname: String
let lname: String
}
struct JsonResponsError: Decodable {
let uid: String
let error: String
}
extension UIView {
func phoneAuth(serverApi path:String, jsonStruct:Codable){
let jsonUrlString = Globals.JOSN_API_URL + path
guard let url = URL(string: jsonUrlString) else {
return
}
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard err == nil else {
return
}
guard let data = data else { return }
do {
let result = try JSONDecoder().decode(jsonStruct.self, from: data)
self.handleJsonResult(resalt: result as AnyObject)
} catch let jsonErr {
print("Error serializing json:", jsonErr)
}
}.resume()
}
func handleJsonResult(resalt:AnyObject){
print(resalt)
}
}
Adding Codable to the inheritance list for Landmark triggers an automatic conformance that satisfies all of the protocol requirements from Encodable and Decodable:
You can Use Codable
struct Landmark: Codable {
var name: String
var foundingYear: Int
// Landmark now supports the Codable methods init(from:) and encode(to:),
// even though they aren't written as part of its declaration.
}
Alternative solution is
func phoneAuth(serverApi path: String, Completion block: #escaping ((Data) -> ())) {
URLSession.shared.dataTask(with: URL(string: url)!) { (data, res, err) in
if let d = data {
block(d)
}
}.resume()
}
Call a Methods
phoneAuth(serverApi: "yourUrl") { (data) in
do {
let result = try JSONDecoder().decode(YourDecodable.self, from: data)
} catch let jsonErr {
print("Error serializing json:", jsonErr)
}
}
You don't have to pass it as a parameter, you can achieve decoding like below
extension UIView {
func phoneAuth(serverApi path:String){
let jsonUrlString = Globals.JOSN_API_URL + path
guard let url = URL(string: jsonUrlString) else {
return
}
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard err == nil else {
return
}
guard let data = data else { return }
do {
let result = try JSONDecoder().decode(JsonRespons.self, from: data)
self.handleJsonResult(resalt: result as AnyObject)
} catch let jsonErr {
print("Error serializing json:", jsonErr)
}
}.resume()
}