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()
}
Related
So I am trying to fetch data from the Pokemon API, and I am getting stuck at the point where I am trying to decode the JSON into a struct. Here is my code:
{
"count":1118,
"next":"https://pokeapi.co/api/v2/pokemon/?offset=20&limit=20",
"previous":null,
"results":
[
{"name":"bulbasaur","url":"https://pokeapi.co/api/v2/pokemon/1/"},
{"name":"ivysaur","url":"https://pokeapi.co/api/v2/pokemon/2/"},
{"name":"venusaur","url":"https://pokeapi.co/api/v2/pokemon/3/"},
{"name":"charmander","url":"https://pokeapi.co/api/v2/pokemon/4/"},
{"name":"charmeleon","url":"https://pokeapi.co/api/v2/pokemon/5/"},
{"name":"charizard","url":"https://pokeapi.co/api/v2/pokemon/6/"},
{"name":"squirtle","url":"https://pokeapi.co/api/v2/pokemon/7/"},
{"name":"wartortle","url":"https://pokeapi.co/api/v2/pokemon/8/"},
{"name":"blastoise","url":"https://pokeapi.co/api/v2/pokemon/9/"},
{"name":"caterpie","url":"https://pokeapi.co/api/v2/pokemon/10/"},
{"name":"metapod","url":"https://pokeapi.co/api/v2/pokemon/11/"},
{"name":"butterfree","url":"https://pokeapi.co/api/v2/pokemon/12/"},
{"name":"weedle","url":"https://pokeapi.co/api/v2/pokemon/13/"},
{"name":"kakuna","url":"https://pokeapi.co/api/v2/pokemon/14/"},
{"name":"beedrill","url":"https://pokeapi.co/api/v2/pokemon/15/"},
{"name":"pidgey","url":"https://pokeapi.co/api/v2/pokemon/16/"},
{"name":"pidgeotto","url":"https://pokeapi.co/api/v2/pokemon/17/"},
{"name":"pidgeot","url":"https://pokeapi.co/api/v2/pokemon/18/"},
{"name":"rattata","url":"https://pokeapi.co/api/v2/pokemon/19/"},
{"name":"raticate","url":"https://pokeapi.co/api/v2/pokemon/20/"}
]
}
func fetchPokemon() {
let defaultSession = URLSession(configuration: .default)
if let url = URL(string: "https://pokeapi.co/api/v2/pokemon/") {
let request = URLRequest(url:url)
let dataTask = defaultSession.dataTask(with: request, completionHandler: { (data, response, error) -> Void in
guard error == nil else {
print ("error: ", error!)
return
}
guard data != nil else {
print("No data object")
return
}
guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else {
print("response is: ", response!)
return
}
guard let mime = response?.mimeType, mime == "application/json" else {
print("Wrong MIME type!")
return
}
DispatchQueue.main.async {
guard let result = try? JSONDecoder().decode(PokemonList.self, from: data!) else {
print("Error Parsing JSON")
return
}
let pokemon = result.pokemon
self.Pokemon = pokemon
print(self.Pokemon)
}
})
dataTask.resume()
}
}
and here is the pokemon struct:
struct Pokemon {
// Various properties of a post that we either need or want to display
let name: String
let url: String
}
extension Pokemon: Decodable {
// properties within a Post returned from the Product Hunt API that we want to extract the info from.
enum PokemonKeys: String, CodingKey {
// first three match our variable names for our Post struct
case name = "name"
case url = "url"
}
init(from decoder: Decoder) throws {
let postsContainer = try decoder.container(keyedBy: PokemonKeys.self)
name = try postsContainer.decode(String.self, forKey: .name)
url = try postsContainer.decode(String.self, forKey: .url)
}
}
struct PokemonList: Decodable {
var pokemon: [Pokemon]
}
It keeps reaching the point when decoding which says "Error Parsing JSON". I'm assuming that there may be an error in how I setup the pokemon struct?
Any ideas?
you are getting a parse error because the data model is not the same. your struct should be:
struct PokemonList: Decodable {
var results: [Pokemon]
var count: Int
var next: String
}
you don't need the extension.
I am trying to decode a JSON object, from this website: https://www.thesportsdb.com/api/v1/json/1/search_all_leagues.php?c=France&s=Soccer
I would like to store them in an array of Soccer elements, and show them in cells.
This is the code I did, but I have key not found errors, how is that possible?
class Soccer: Codable {
var strLeague: String
var strDescriptionEN: String
var strBadge: String
var strDivision: String
var intFormedYear: String
var strCountry: String
init(strLeague: String, strDescriptionEN: String, strBadge: String, strDivision: String, intFormedYear: String, strCountry: String) {
self.strLeague = strLeague
self.strDescriptionEN = strDescriptionEN
self.strBadge = strBadge
self.strDivision = strDivision
self.intFormedYear = intFormedYear
self.strCountry = strCountry
}
}
class SoccerTVC: UITableViewController {
var leagues = [Soccer]()
func download(at url: String, handler: #escaping (Data?) -> Void)
{
// 1 - Create URL
guard let url = URL(string: url) else {
debugPrint("Failed to create URL")
handler(nil)
return
}
// 2 - Create GET Request
var request: URLRequest = URLRequest(url: url)
request.httpMethod = "GET"
// 3 - Create download task, handler will be called when request ended
let task = URLSession.shared.dataTask(with: request) {
(data, response, error) in handler(data)
}
task.resume()
}
func getSoccer() {
// 1 - Download Soccer
download(at: "https://www.thesportsdb.com/api/v1/json/1/search_all_leagues.php?c=France&s=Soccer")
{ (SoccerData) in
if let Soccerdata = SoccerData {
// 2 - Decode JSON into a array of Game object
let decoder: JSONDecoder = JSONDecoder()
do {
let jsonData = [try decoder.decode(Soccer.self, from: Soccerdata)]
self.leagues = jsonData
debugPrint(self.leagues)
DispatchQueue.main.sync {
self.tableView.reloadData()
}
}
catch {
debugPrint("Failed to parse data - error: \(error)")
}
}
else
{
debugPrint("Failed to get soccer data")
}
}
}
override func viewDidLoad() {
getSoccer()
super.viewDidLoad()
}
}
Error message:
Failed to parse data - error: keyNotFound(CodingKeys(stringValue:
"strLeague", intValue: nil), Swift.DecodingError.Context(codingPath:
[], debugDescription: "No value associated with key
CodingKeys(stringValue: \"strLeague\", intValue: nil)
(\"strLeague\").", underlyingError: nil))
Your desired values is in country array key so you need to change your struct with :
struct Soccer: Codable {
let countrys : [Country]?
}
struct Country : Codable{
var strLeague: String
var strDescriptionEN: String
var strBadge: String
....
}
And you need to change your json decoder like :
var leagues = [Country]()
let jsonData = try decoder.decode(Soccer.self, from: Soccerdata)
self.leagues = jsonData.countrys
}
Try this instead:
let jsonData = try JSONDecoder().decode([String:[Soccer]].self, from: Soccerdata)
self.leagues = jsonData.values
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
I am confusing to getting detail of fruit
{
"fruits": [
{
"id": "1",
"image": "https://cdn1.medicalnewstoday.com/content/images/headlines/271/271157/bananas.jpg",
"name": "Banana"
},
{
"id": "2",
"image": "http://soappotions.com/wp-content/uploads/2017/10/orange.jpg",
"title": "Orange"
}
]
}
Want to parse JSON using "Decodable"
struct Fruits: Decodable {
let Fruits: [fruit]
}
struct fruit: Decodable {
let id: Int?
let image: String?
let name: String?
}
let url = URL(string: "https://www.JSONData.com/fruits")
URLSession.shared.dataTask(with: url!) { (data, response, error) in
guard let data = data else { return }
do{
let fruits = try JSONDecoder().decode(Fruits.self, from: data)
print(Fruits)
}catch {
print("Parse Error")
}
also can you please suggest me cocoapod library for fastly download images
The issue you are facing is because your JSON is returning different data for your Fruits.
For the 1st ID it returns a String called name, but in the 2nd it returns a String called title.
In addition when parsing the JSON the ID appears to be a String and not an Int.
Thus you have two optional values from your data.
As such your Decodable Structure should look something like this:
struct Response: Decodable {
let fruits: [Fruits]
}
struct Fruits: Decodable {
let id: String
let image: String
let name: String?
let title: String?
}
Since your URL doesn't seem to be valid, I created the JSON file in my main bundle and was able to parse it correctly like so:
/// Parses The JSON
func parseJSON(){
if let path = Bundle.main.path(forResource: "fruits", ofType: "json") {
do {
let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe)
let jsonResult = try JSONDecoder().decode(Response.self, from: data)
let fruitsArray = jsonResult.fruits
for fruit in fruitsArray{
print("""
ID = \(fruit.id)
Image = \(fruit.image)
""")
if let validName = fruit.name{
print("Name = \(validName)")
}
if let validTitle = fruit.title{
print("Title = \(validTitle)")
}
}
} catch {
print(error)
}
}
}
Hope it helps...
// Parse Json using decodable
// First in create Structure depends on json
//
//
//
struct Countory : Decodable {
let name: String
let capital: String
let region: String
}
let url = "https://restcountries.eu/rest/v2/all"
let urlObj = URL(string: url)!
URLSession.shared.dataTask(with: urlObj) {(data, responds, Error) in
do {
var countories = try JSONDecoder().decode([Countory].self, from: data!)
for country in countories {
print("Country",country.name)
print("###################")
print("Capital",country.capital)
}
} catch {
print(" not ")
}
}.resume()
Model sample:
public struct JsonData: Codable{
let data: [Data]?
let meta: MetaValue?
let linksData: LinksValue?
private enum CodingKeys: String, CodingKey{
case data
case meta
case linksData = "links"
}
}
enum BackendError: Error {
case urlError(reason: String)
case objectSerialization(reason: String)
}
struct APIServiceRequest {
static func serviceRequest<T>(reqURLString: String,
resultStruct: T.Type,
completionHandler:#escaping ((Any?, Error?) -> ())) where T : Decodable {
guard let url = URL(string: reqURLString) else {
print("Error: cannot create URL")
let error = BackendError.urlError(reason: "Could not construct URL")
completionHandler(nil, error)
return
}
let urlRequest = URLRequest(url: url)
let session = URLSession.shared
let task = session.dataTask(with: urlRequest) { (data, response, error) in
guard error == nil else {
completionHandler(nil, error)
return
}
guard let responseData = data else {
print("Error: did not receive data")
let error = BackendError.objectSerialization(reason: "No data in response")
completionHandler(nil, error)
return
}
let decoder = JSONDecoder()
do {
let books = try decoder.decode(resultStruct, from: responseData)
completionHandler(books, nil)
} catch {
print("error trying to convert data to JSON")
print(error)
completionHandler(nil, error)
}
}
task.resume()
}
}
To Access:
let apiService = APIServiceRequest()
var dataArray: [String: Any]? //global var
apiService.serviceRequest(reqURLString: endPoint, resultStruct: VariantsModel.self, completionHandler: {dataArray,Error in})
POST Method
func loginWS(endpoint: String, completionHandler: #escaping (Any?) -> Swift.Void) {
guard let sourceUrl = URL(string: endpoint) else { return }
let request = NSMutableURLRequest(url: sourceUrl)
let session = URLSession.shared
request.httpMethod = "POST"
request.addValue(vehiceHeader, forHTTPHeaderField: "X-Vehicle-Type")
request.addValue(contentHeader, forHTTPHeaderField: "Content-Type")
let task = session.dataTask(with: request as URLRequest) { data, response, error in
guard let data = data else { return }
do {
let responseData = try JSONDecoder().decode(JsonData.self, from: data)
print("response data:", responseData)
completionHandler(responseData)
} catch let err {
print("Err", err)
}
}.resume()
}
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.