Hi I'm a beginner at programming and I followed a tutorial online to parse information from a Json file. In order to get an URL link, I created a local server that contains a json file. When I start the program I get a mistake.
Called Thread 2: signal SIGABRT for the code snippet
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as! [String: AnyObject]
Google says there is a disconnected outlet, but the outlets in my code are connected.
There are also multiple error warnings, and google says there is a problem with an array and I have to set it to NSDictionary, but I never declared any variable as an array or a dictionary.
Could not cast value of type '__NSArrayM' (0x7fff80919120) to 'NSDictionary' (0x7fff809193b0).
2021-06-30 22:01:07.257592+0200 Booknerd3.0[71572:3475394] Could not cast value of type '__NSArrayM' (0x7fff80919120) to 'NSDictionary' (0x7fff809193b0).
Could not cast value of type '__NSArrayM' (0x7fff80919120) to 'NSDictionary' (0x7fff809193b0).
CoreSimulator 757.5 - Device: iPhone 8 (A2400EB4-8BA3-4297-9DF3-1743AA0BB7E4) - Runtime: iOS 14.5 (18E182) - DeviceType: iPhone 8
Json file:
{
"books":
[
{
"author": "Chinua Achebe",
"country": "Nigeria",
"imageLink": "https://s3.amazonaws.com/AKIAJC5RLADLUMVRPFDQ.book-thumb-images/ableson.jpg",
"language": "English",
"link": "https://en.wikipedia.org/wiki/Things_Fall_Apart\n",
"pages": 209,
"title": "Things Fall Apart",
"year": 1958
},
{
"author": "Hans Christian Andersen",
"country": "Denmark",
"imageLink": "https://s3.amazonaws.com/AKIAJC5RLADLUMVRPFDQ.book-thumb-images/ableson2.jpg",
"language": "Danish",
"link": "https://en.wikipedia.org/wiki/Fairy_Tales_Told_for_Children._First_Collection.\n",
"pages": 784,
"title": "Fairy tales",
"year": 1836
}
]
}
My view controller
import UIKit
class MainScreenViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableview: UITableView!
var books: [Books]? = []
override func viewDidLoad() {
super.viewDidLoad()
fetchArticle()
}
func fetchArticle(){
let urlRequest = URLRequest(url: URL(string: "http://localhost:3000/books")!)
let task = URLSession.shared.dataTask(with: urlRequest) { (data,response,error) in
if error != nil {
print(error)
return
}
self.books = [Books]()
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as! [String: AnyObject]
if let booksFromJson = json["books"] as? [[String : AnyObject]]{
for bookFromJson in booksFromJson {
let book = Books()
if let title = bookFromJson["title"] as? String, let author = bookFromJson["author"] as? String, let imageLink = bookFromJson["imageLink"] as? String {
book.author = author
book.title = title
book.imageLink = imageLink
}
self.books?.append(book)
}
}
DispatchQueue.main.async {
self.tableview.reloadData()
}
} catch let error {
print(error)
}
}
task.resume()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "bookCell", for: indexPath) as! BookCell
cell.title.text = self.books?[indexPath.item].title
cell.author.text = self.books?[indexPath.item].author
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.books?.count ?? 0
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
}
}
Short answer your Json is a Dictionary that contains a key with a value of array of dictionary. When you decode you're saying it will be a dictionary key of books and value of array of array of dictionary.
if let booksFromJson = json["books"] as? [[String : AnyObject]]...
an array of dictionary is
[String:Any]
more specifically in your case
[String:[String: Any]]
A lot of force unwraps, assumptions about the type. Just don't if you want to have a stable app. Most likely you're looking for usage of Decodable. You can declare two structs to reflect the JSON schema.
For your example it would be:
struct BooksResponse: Decodable {
let books: [Book]
}
struct Book: Decodable {
let author: String
let imageLink: URL
let title: String
}
Which can be used in your code with few modifications.
// This part declared somewhere
enum JsonBooksDecodingError: Error {
case dataCorrupted
}
// --
func fetchArticle() {
guard let urlRequest = URLRequest(url: URL(string: "http://localhost:3000/books")) else {
assert(false, "URL broken")
}
URLSession.shared.dataTask(with: urlRequest) { (data,response,error) in
if error != nil {
print(error)
return
}
do {
let booksResponse = try handle(booksData: data)
self.books = booksResponse.books
DispatchQueue.main.async {
self.tableview.reloadData()
}
}
} catch let error {
print(error)
}
}
.resume()
}
func handle(booksData data: Data) throws -> BooksResponse {
guard let jsonData = jsonData else {
throw JsonBooksDecodingError.dataCorrupted
}
return try JSONDecoder().decode(BooksResponse.self, from: jsonData)
}
No force unwraps, no guessing, zero crashes :). This approach lets you have custom decoding strategies as well. You can read more about Codable (Decodable & Encodable) here
Related
Hi I have problems to read the Json Data into the TableView.
Can anybody help me, its my first time working with it.
I know I am doing something wrong, but I cant finde the Solution for my JSON File cause in the Internet they use simple ones..
Here is my JSON:
{
"data":[
{
"title": "Brot",
"desc":[
{
"Name": "Roggenschrot- und Roggenvollkornbrot",
"Menge": "Gramm",
"Kalorie": "2",
"Energiedichte": "Gelb"
},
{
"Name": "Weizenschrot- und Weizenvollkornbrot",
"Menge": "Gramm",
"Kalorie": "2",
"Energiedichte": "Gelb"
},
{
"Name": "Weizenschrot- und Weizenvollkornbrot",
"Menge": "Gramm",
"Kalorie": "2",
"Energiedichte": "Gelb"
},
]
}
]
}
Here is my tableView
import UIKit
class EseenTagebuchTableViewController: UITableViewController {
var result: Result?
var resultItem: ResultItem?
override func viewDidLoad() {
super.viewDidLoad()
parseJSON()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return result?.data.count ?? 0
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return result?.data[section].title
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
if let result = result {
return result.data.count
}
return 0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let text = resultItem?.desc?[indexPath.section].Name?[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = text
return cell
}
private func parseJSON() {
guard let path = Bundle.main.path(forResource: "Ernahrungstagebuch", ofType: "json") else {
return
}
let url = URL(fileURLWithPath: path)
do {
let jsonData = try Data(contentsOf: url)
result = try JSONDecoder().decode(Result.self, from: jsonData)
return
}
catch {
print("Error: \(error)")
}
}
}
And here are my Model 1 and after my Model 2
import Foundation
struct Result: Codable {
let data: [ResultItem]
}
struct ResultItem: Codable {
let title: String
let desc: [descItems]?
}
Model 2
import Foundation
struct descItems: Codable {
let Name: String?
let Menge: String?
let Kalorie: Int?
let Energiedichte: String?
}
What am I doing wrong?
First your json data in your file is not correct, there is an extra "," comma after
the last "Energiedichte": "Gelb" , remove that from your file.
Second your model is not correct, you should have:
struct Result: Codable {
let data: [ResultItem]
}
struct ResultItem: Codable {
let title: String
let desc: [DescItems]?
}
struct DescItems: Codable {
let Name: String?
let Menge: String?
let Kalorie: String? // <-- here not Int?
let Energiedichte: String?
}
Using your code parseJSON() I was able to parse the data without errors.
EDIT-1: if you really want kalories to be Int, try:
struct DescItems: Codable {
let name: String?
let menge: String?
let kalorie: Int? // <-- here
let energiedichte: String?
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
menge = try container.decode(String.self, forKey: .menge)
energiedichte = try container.decode(String.self, forKey: .energiedichte)
let calorie = try container.decode(String.self, forKey: .kalorie) // <-- here
kalorie = Int(calorie) // <-- here
}
enum CodingKeys: String, CodingKey {
case name = "Name"
case menge = "Menge"
case kalorie = "Kalorie"
case energiedichte = "Energiedichte"
}
}
Note the change of case, as per common practice.
you can also use json parse below code:
func parseJSON() {
let path = Bundle.main.path(forResource: "Ernahrungstagebuch", ofType: "json")
let jsonData = try? NSData(contentsOfFile: path!, options: NSData.ReadingOptions.mappedIfSafe)
let jsonResult: NSDictionary = try! (JSONSerialization.jsonObject(with: jsonData! as Data, options: JSONSerialization.ReadingOptions.mutableContainers) as? NSDictionary)!
if let data = jsonResult["data"] as? [[String:Any]] {
self.arrPlan = data.map{Day(JSON: $0)!}
// print(arrPlan)
}
}
Try to this code for get data array object and after reload tableview Data.
You can use this extension to parse json.
import Foundation
extension Bundle {
func decode<T: Decodable>(_ type: T.Type, from file: String, dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .deferredToDate, keyDecodingStrategy: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys) -> T {
guard let url = self.url(forResource: file, withExtension: nil) else {
fatalError("Failed to locate \(file) in bundle.")
}
guard let data = try? Data(contentsOf: url) else {
fatalError("Failed to load \(file) from bundle.")
}
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = dateDecodingStrategy
decoder.keyDecodingStrategy = keyDecodingStrategy
do {
return try decoder.decode(T.self, from: data)
} catch DecodingError.keyNotFound(let key, let context) {
fatalError("Failed to decode \(file) from bundle due to missing key '\(key.stringValue)' not found – \(context.debugDescription)")
} catch DecodingError.typeMismatch(_, let context) {
fatalError("Failed to decode \(file) from bundle due to type mismatch – \(context.debugDescription)")
} catch DecodingError.valueNotFound(let type, let context) {
fatalError("Failed to decode \(file) from bundle due to missing \(type) value – \(context.debugDescription)")
} catch DecodingError.dataCorrupted(_) {
fatalError("Failed to decode \(file) from bundle because it appears to be invalid JSON")
} catch {
fatalError("Failed to decode \(file) from bundle: \(error.localizedDescription)")
}
}
}
And use it like this:
let incomingData = Bundle.main.decode([Your_Model].self, from: "Your_Json_File_name.json")
// my url
// https://fetch-hiring.s3.amazonaws.com/hiring.json
/*
my json
[
{"id": 383, "listId": 4, "name": ""},
{"id": 472, "listId": 1, "name": ""},
{"id": 625, "listId": 2, "name": null}
]
*/
// my main vc class for table view controller
import UIKit
class HeadlLinesTableViewController: UITableViewController {
var parse = [HiringElement]()
override func viewDidLoad() {
// Do any additional setup after loading the view.
super.viewDidLoad()
// Do any additional setup after loading the view.
let urlString = "https://fetch-hiring.s3.amazonaws.com/hiring.json"
guard let url = URL(string: urlString) else { return }
// 2
URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!.localizedDescription)
}
guard let data = data else { return }
// 3
//Decode data
self.Elements = try? JSONDecoder().decode(HiringElement.self, from: data)
print(data)
// 4
//Get back to the main queue
DispatchQueue.main.async {
self.tableView.reloadData()
}
// 5
}.resume() // fires of request
}
My model struct for my api this is something I used from quickTypeIo api generator
struct HiringElement: Codable {
let id, listID: Int
let name: String?
enum CodingKeys: String, CodingKey {
case id
case listID
case name
}
} typealias Hiring = [HiringElement]
And my table view controller method here I can't display data on and some some errors. I am using tableview controller so doesn't need tableview delegate or datasource
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
guard let articles = Elements else { return 0 }
return return parse.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
as? newsTableViewCell else {
fatalError(" cell not found ") }
// here I have errors thanks
cell.titleLabel.text = parse[indexPath.row].name
print(cell.titleLabel.text)
return cell
}
}
Here is my table view cell class
import UIKit
class newsTableViewCell: UITableViewCell {
//var article:Article!
#IBOutlet weak var avator: UIImageView!
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var newsLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
I think you should work on the decoding part. Here is a solution:
struct HiringElement: Decodable {
let id, listId: Int
let name: String?
}
#propertyWrapper
struct IgnoreFailure<Value: Decodable>: Decodable {
var wrappedValue: [Value] = []
private struct _None: Decodable {}
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
while !container.isAtEnd {
if let decoded = try? container.decode(Value.self) {
wrappedValue.append(decoded)
}
else {
try? container.decode(_None.self)
}
}
}
}
Then write the following code in your HeadlLinesTableViewController.swift.
typealias ArrayIgnoringFailure<Value: Decodable> = IgnoreFailure<Value>
Then try decoding as:
guard let objects = try? JSONDecoder().decode(ArrayIgnoringFailure<HiringElement>.self, from: data) else { return }
self.elements = objects.wrappedValue
Hope it will solve your problems.
I am writing an app that needs to look at a local JSON file, then compare it's version to one I have hosted on a website. If they don't match, download the one from the web and save it locally. If they do match, then continue on and use the local JSON file. This version info is in the JSON file itself.
Previously, my app would simply parse the online data and use that directly. It would then populate the UITableView using the JSON data. Now that I am using my local file, the UITableView isn't getting populating, and I'm not certain how to fix it. From reading the new function, I think my issue is that I'm not using JSONDecoder(), and instead using JSONSerialization(), and therefore I can't point it at the specific metadata I want.
26 Jun 18 Edit (Below is my BonusListViewController.swift file):
//
// BonusListViewController.swift
// Tour of Honor
//
// Created by Tommy Craft on 6/6/18.
// Copyright © 2018 Tommy Craft. All rights reserved.
//
import UIKit
import os.log
import Foundation
class BonusListViewController: UITableViewController {
var bonuses = [JsonFile.JsonBonuses]()
let defaults = UserDefaults.standard
override func viewDidLoad() {
super.viewDidLoad()
// MARK: Data Structures
// Settings Struct
struct Constants {
struct RiderData {
let riderNumToH = "riderNumToH"
let pillionNumToH = "pillionNumToH"
}
struct RallyData {
let emailDestinationToH = "emailDestinationToH"
}
}
//MARK: Check for updated JSON file
checkJSON()
//MARK: Trigger JSON Download
/*
downloadJSON {
print("downloadJSON Method Called")
}
*/
}
// MARK: - Table View Configuration
// MARK: Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
print("Found \(bonuses.count) sections.")
return bonuses.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("Found \(bonuses.count) rows in section.")
return bonuses.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
cell.textLabel?.text = bonuses[indexPath.section].name.capitalized
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "showDetail", sender: self)
}
// MARK: - Table View Header
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 30
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return bonuses[section].state
}
override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 3
}
// MARK: Functions
// MARK: - Download JSON from ToH webserver
func downloadJSON(completed: #escaping () -> ()) {
let url = URL(string: "http://tourofhonor.com/BonusData.json")
URLSession.shared.dataTask(with: url!) { [weak self] (data, response, error) in
if error == nil {
do {
let posts = try JSONDecoder().decode(JsonFile.self, from: data!)
DispatchQueue.main.async {
completed()
}
print("Downloading Updated JSON (Version \(posts.meta.version))")
print(posts.bonuses.map {$0.bonusCode})
print(posts.bonuses.map {$0.state})
self?.bonuses = posts.bonuses
self?.defaults.set("downloadJSON", forKey: "jsonVersion") //Set version of JSON for comparison later
DispatchQueue.main.async {
//reload table in the main queue
self?.tableView.reloadData()
}
} catch {
print("JSON Download Failed")
}
}
}.resume()
}
func checkJSON() {
//MARK: Check for updated JSON file
let defaults = UserDefaults.standard
let hostedJSONFile = "http://tourofhonor.com/BonusData.json"
let jsonURL = URL(string: hostedJSONFile)
var hostedJSONVersion = ""
let jsonData = try! Data(contentsOf: jsonURL!)
let jsonFile = try! JSONSerialization.jsonObject(with: jsonData, options: .mutableContainers) as! [String : Any]
let metaData = jsonFile["meta"] as! [String : Any]
hostedJSONVersion = metaData["version"] as! String
let localJSONVersion = defaults.string(forKey: "jsonVersion")
if localJSONVersion != hostedJSONVersion {
print("L:\(localJSONVersion!) / H:\(hostedJSONVersion)")
print("Version Mismatch: Retrieving lastest JSON from server.")
updateJSONFile()
} else {
//Retrieve the existing JSON from documents directory
print("L:\(localJSONVersion!) / H:\(hostedJSONVersion)")
print("Version Match: Using local file.")
let fileURL = defaults.url(forKey: "pathForJSON")
do {
let localJSONFileData = try Data(contentsOf: fileURL!, options: [])
let myJson = try JSONSerialization.jsonObject(with: localJSONFileData, options: .mutableContainers) as! [String : Any]
//Use my downloaded JSON file to do stuff
print(myJson)
DispatchQueue.main.async {
//reload table in the main queue
self.tableView.reloadData()
}
} catch {
print(error)
}
}
}
func updateJSONFile() {
print("updateJSONFile Method Called")
let hostedJSONFile = "http://tourofhonor.com/BonusData.json"
let jsonURL = URL(string: hostedJSONFile)
let itemName = "BonusData.json"
let defaults = UserDefaults.standard
do {
let directory = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor:nil, create:false)
let fileURL = directory.appendingPathComponent(itemName)
let jsonData = try Data(contentsOf: jsonURL!)
let jsonFile = try JSONSerialization.jsonObject(with: jsonData, options: .mutableContainers) as? [String : Any]
let metaData = jsonFile!["meta"] as! [String : Any]
let jsonVersion = metaData["version"]
print("JSON VERSION ", jsonVersion!)
try jsonData.write(to: fileURL, options: .atomic)
defaults.set(fileURL, forKey: "pathForJSON") //Save the location of your JSON file to UserDefaults
defaults.set(jsonVersion, forKey: "jsonVersion") //Save the version of your JSON file to UserDefaults
DispatchQueue.main.async {
//reload table in the main queue
self.tableView.reloadData()
}
} catch {
print(error)
}
}
// MARK: - Navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? BonusDetailViewController {
destination.bonus = bonuses[(tableView.indexPathForSelectedRow?.row)!]
}
}
}
And here is the JsonFile.swift:
import Foundation
struct JsonFile: Codable {
struct Meta: Codable {
let fileName: String
let version: String
}
struct JsonBonuses: Codable {
let bonusCode: String
let category: String
let name: String
let value: Int
let city: String
let state: String
let flavor: String
let imageName: String
}
let meta: Meta
let bonuses: [JsonBonuses]
}
Is this related to not using JSONDecoder() in my updated version or am I going down the wrong path there? Also, how do I get this new data to work with the UITableView?
First of all, you are parsing JSON values incorrectly. You need to first understand your JSON format. You go to your JSON file link, and analyze it. If it starts with a "{", then it is a Dictionary, if it starts with a "[", then it is an Array. In your case, it is a Dictionary, then there come the keys which are Strings ("meta", "bonuses"). So, we know our keys are Strings. Next, we look at our values. For "meta" we have a Dictionary of String : String; for "bonuses" we have an Array of Dictionaries.
So, our JSON format is [String : Any], or it can be written Dictionary<String, Any>.
Next step, is accessing those values in the Dictionary.
func updateJSONFile() {
print("updateJSONFile Method Called")
let hostedJSONFile = "http://tourofhonor.com/BonusData.json"
let jsonURL = URL(string: hostedJSONFile)
let itemName = "BonusData.json"
let defaults = UserDefaults.standard
do {
let directory = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor:nil, create:false)
let fileURL = directory.appendingPathComponent(itemName)
let jsonData = try Data(contentsOf: jsonURL!)
let jsonFile = try JSONSerialization.jsonObject(with: jsonData, options: .mutableContainers) as? [String : Any]
let metaData = jsonFile!["meta"] as! [String : Any]
let jsonVersion = metaData["version"]
print("JSON VERSION ", jsonVersion!)
try jsonData.write(to: fileURL, options: .atomic)
defaults.set(fileURL, forKey: "pathForJSON") //Save the location of your JSON file to UserDefaults
defaults.set(jsonVersion, forKey: "jsonVersion") //Save the version of your JSON file to UserDefaults
DispatchQueue.main.async {
//reload table in the main queue
self.tableView.reloadData()
}
} catch {
print(error)
}
}
Then, when you access your locally saved file, again, you have to parse the JSON to check the versions:
func checkJSON() {
//MARK: Check for updated JSON file
let defaults = UserDefaults.standard
let hostedJSONFile = "http://tourofhonor.com/BonusData.json"
let jsonURL = URL(string: hostedJSONFile)
var hostedJSONVersion = ""
let jsonData = try! Data(contentsOf: jsonURL!)
let jsonFile = try! JSONSerialization.jsonObject(with: jsonData, options: .mutableContainers) as! [String : Any]
let metaData = jsonFile["meta"] as! [String : Any]
hostedJSONVersion = metaData["version"] as! String
let localJSONVersion = defaults.string(forKey: "jsonVersion")
if localJSONVersion != hostedJSONVersion {
print("\(localJSONVersion) : \(hostedJSONVersion)")
updateJSONFile()
} else {
//Retrieve the existing JSON from documents directory
print("\(localJSONVersion) : \(hostedJSONVersion)")
print("Local JSON is still the latest version")
let fileUrl = defaults.url(forKey: "pathForJSON")
do {
let localJSONFileData = try Data(contentsOf: fileUrl!, options: [])
let myJson = try JSONSerialization.jsonObject(with: localJSONFileData, options: .mutableContainers) as! [String : Any]
//Use my downloaded JSON file to do stuff
DispatchQueue.main.async {
//reload table in the main queue
self.tableView.reloadData()
}
} catch {
print(error)
}
}
}
Don't forget to allow arbitrary loads in your Info.plist file, because your JSON file is hosted on a website without https.
I passed a JSON data to this table view controller. How to get the JSON data and show it on a table view cell?
When I print passedData I receive the following output:
["jobs": <__NSArrayM 0x17005d9d0>
({
jobDate = "2017-08-31";
jobEndTime = 1504144800;
jobID = 87;
jobTime = 1504137600;
},
{
jobDate = "2017-08-31";
jobEndTime = 1504173600;
jobID = 89;
jobTime = 1504170000;
}),
"result": success,
"message": Retrieve Sucessfully]
This is the code I'm using:
var passedData: [String: Any]!
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard let jobs = passedData["jobs"] as? [[String:Any]] else {return 0}
return jobs.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "jobCell", for: indexPath)
// jobs[indexPath.row] display jobTime
return cell
}
Instead of giving you a reply on the subject I would try to help understanding the domain you are working with.
The JSON response you are retrieving contains an array (jobs) where each element is an object. In fact, the JSON syntax states that:
In JSON, values must be one of the following data types:
a string
a number
an object (JSON object)
an array
a boolean
null
If you are using JSONSerialization class, then you will have a dictionary that will contain an array of dictionaries.
A simple snippet like this will give you that array
if let jsonArray = jsonDict["jobs"] as? [[String: Any]] {
print(jsonArray)
}
Now, in order to access elements of that array you can do like the following:
let jsonArrayDict = jsonArray[0]
print(jsonArrayDict["jobTime"] ?? 0)
Obviously the code here is not production ready since you need to pay attention to possible crashes of your app.
What I really suggest is to work with a model that can be passed to your table view cell. This approach has these benefits:
avoid using optionals
document your code
unit test your code
etc.
Here an example on how to convert your JSON object into a specific model. Run it in a playground and practice.
struct Job {
let jobDate: String
let jobEndTime: Int
let jobID: Int
let jobTime: Int
}
extension Job {
init?(dict: [String: Any]) {
guard let jobDate = dict["jobDate"] as? String,
let jobEndTime = dict["jobEndTime"] as? Int,
let jobID = dict["jobID"] as? Int,
let jobTime = dict["jobTime"] as? Int else {
return nil
}
self.jobDate = jobDate
self.jobEndTime = jobEndTime
self.jobID = jobID
self.jobTime = jobTime
}
}
extension Job: CustomStringConvertible {
var description: String {
return "Job: \(jobDate) \(jobEndTime) \(jobID) \(jobTime)"
}
}
let jsonString = """
{
"jobs": [
{
"jobDate": "2017-08-31",
"jobEndTime": 1504144800,
"jobID": 87,
"jobTime": 1504137600
},
{
"jobDate2": "2017-08-31",
"jobEndTime": 1504144800,
"jobID": 87,
"jobTime": 1504137600
}
],
"result": "success",
"message": "Retrieve Sucessfully"
}
"""
if let jsonData = jsonString.data(using: .utf8), let jsonObject = try? JSONSerialization.jsonObject(with: jsonData, options: []),
let jsonDict = jsonObject as? [String: Any],
let jsonArray = jsonDict["jobs"] as? [[String: Any]] {
let jobs = jsonArray.flatMap { Job(dict: $0) }
print(jobs)
} else {
print("No Results")
}
This should help you understand the steps needed to parse and use the JSON data that you have. You will likely need to create a custom UITableViewCell which is beyond the scope of this answer. There are plenty of resources online which will explain this part of the process.
You will also need to convert your timestamps to dates, there are alot of answers on StackOverflow that can help with this. Like this one
Mapping JSON Data
let passedJsonStr = "{\"jobs\":[{\"jobDate\":\"2017-08-31\",\"jobEndTime\":1504144800,\"jobID\":87,\"jobTime\":1504137600},{\"jobDate\":\"2017-08-31\",\"jobEndTime\":1504173600,\"jobID\":89,\"jobTime\":1504170000}],\"result\":\"success\",\"message\":\"Retrieve Sucessfully\"}"
struct Job {
var jobDate: String
var jobEndTime: Int
var jobID: Int
var jobTime: Int
init(dict: [String:AnyObject]) {
// unwrap these safely, I'm just giving an example
self.jobDate = dict["jobDate"] as! String
self.jobEndTime = dict["jobEndTime"] as! Int
self.jobID = dict["jobID"] as! Int
self.jobTime = dict["jobTime"] as! Int
}
}
var jobs = [Job]()
if let data = passedJsonStr.data(using: String.Encoding.utf8) {
if let jsonObject = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String:AnyObject] {
if let jsonData = jsonObject["jobs"] as? [[String:AnyObject]] {
jobs = jsonData.map { Job(dict: $0) }
}
print(jobs)
}
}
Displaying in UITableView
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "jobCell", for: indexPath)
let job = self.jobs[indexPath.row]
cell.titleLabel.text = job.jobDate
return cell
}
I have the json below but unable to figure out how to parse it in Swift 3. My code is below. The json from the API has an array root. I am using Xcode 8.2.1 with Swift 4 and Alamofire 4.0.
["items": <__NSArrayM 0x608000248af0>(
{
currency = USD;
image = "https://cdn.myDomain.com/image.jpg";
"item_title" = "Antique Table";
"name:" = "";
price = 675;
},
{
currency = USD;
image = "https://cdn.mydomain.com/image2.jpg";
"name:" = "";
price = 950;
...
Here is my code. I have tried to get an array r dictionary from the results but it's always nil.
Alamofire.request(myURL)
.responseJSON(completionHandler: {
response in
self.parseData(JSONData: response.data!)
})
}
func parseData(JSONData: Data) {
do {
let readableJSON = try JSONSerialization.jsonObject(with: JSONData, options:.mutableContainers) as! [String: Any]
print(readableJSON)
}
catch {
print(error)
}
}
I have tried this let item = readableJSON["items"] as? [[String: Any]] as suggested here but it would not compile with an error [String:Any] has no subscript and let item = readableJSON["items"] as? [String: Any]! compiles with a warning Expression implicitly coerced from string but produces nil. Parsing this json is life or death for me.
Do something like
let responseJSON = response.result.value as! [String:AnyObject]
then you'll be able to access elements in that dictionary like so:
let infoElementString = responseJSON["infoElement"] as! String
This was the parse json function I eventually came up with. The problem for this json data is that it is a dictionary inside an array. I am a noob and most of the answers and how tos I saw would not fit my json data. Here is the function I finally came up with with worked.
var myItems = [[String:Any]]()
then in my view controller class
func loadMyItems() {
Alamofire.request(myItemsURL)
.responseJSON(completionHandler: {
response in
self.parseData(JSONData: response.data!)
self.collectionView.reloadData()
})
}
func parseData(JSONData: Data) {
do {
let readableJSON = try JSONSerialization.jsonObject(with: JSONData, options:.allowFragments) as! [String: Any]
let items = readableJSON["items"] as! [[String: Any]]
self.myItems = items
}
catch {
print(error)
}
}
func collectionView(_ collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "myCell", for: indexPath) as? myCell
let dictionary = myItems[indexPath.row] as [String:Any]
if let item_title = dictionary["item_title"] as? String {
cell!.textLabel.text = item_title
print(item_title)
}
return cell!
}
Alamofire Example in Swift 3
1.First of all Use two cocoapods to your project.Use SwiftyJSON for json parse
pod 'Alamofire'
pod 'SwiftyJSON'
My Json is below
{"loginNodes":[{"errorMessage":"Welcome To Alamofire","name":Enamul Haque,"errorCode":"0","photo":null}]}
It may be done in different way. But I have done below Way. Note if you don't need any parameter to send the server then remove parameter option. It may work post or get method. You can use any way. My Alamofire code is below...which is working fine for me......
Alamofire.request("http://era.com.bd/UserSignInSV", method: .post,parameters:["uname":txtUserId.text!,"pass":txtPassword.text!]).responseJSON{(responseData) -> Void in
if((responseData.result.value != nil)){
let jsonData = JSON(responseData.result.value)
if let arrJSON = jsonData["loginNodes"].arrayObject {
for index in 0...arrJSON.count-1 {
let aObject = arrJSON[index] as! [String : AnyObject]
let errorCode = aObject["errorCode"] as? String;
let errorMessage = aObject["errorMessage"] as? String;
if("0"==errorCode ){
//Database Login Success Action
}else{
// //Database Login Fail Action
}
}
}
}
}
If You use Like table View Or Collection View or so on, you can use like that..
Declare A Array
var arrRes = [String:AnyObject]
Assign the value to array like
if((responseData.result.value != nil)){
// let jsonData = JSON(responseData.result.value)
if((responseData.result.value != nil)){
let swiftyJsonVar = JSON(responseData.result.value!)
if let resData = swiftyJsonVar["loginNodes"].arrayObject {
self.arrRes = resData as! [[String:AnyObject]]
}
if self.arrRes.count > 0 {
self.tableView.reloadData()
}
}
}
In taleView, cellForRowAt indexPath , Just use
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! customCell
cell.errorLabelName.text = arrRes[indexPath.row]["errorMessage"] as? String
Swift 3
Alamofire Example in Swift 3
import UIKit
import Alamofire
import SwiftyJSON
class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource
{
var array = [[String:AnyObject]]()
#IBOutlet weak var tableview: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
Alamofire.request("http://www.designer321.com/johnsagar/plumbingapp/webservice/list_advertise.php?zip=123456").responseJSON { (responseData) -> Void in
if((responseData.result.value) != nil)
{
let swiftyJsonVar = JSON(responseData.result.value!)
print("Main Responce")
print(swiftyJsonVar)
}
if let result = responseData.result.value
{
if let Res = (result as AnyObject).value(forKey: "response") as? NSDictionary
{
if let Hallo = (Res as AnyObject).value(forKey: "advertise_list") as? NSArray
{
print("-=-=-=-=-=-=-")
print(Hallo)
self.array = Hallo as! [[String:AnyObject]]
print(self.array)
}
}
self.tableview.reloadData()
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return array.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath) as! TableViewCell
var dict = array[indexPath.row]
cell.lbl1.text = dict["address"] as? String
cell.lbl2.text = dict["ad_created_date"] as? String
cell.lbl3.text = dict["phone_number"] as? String
cell.lbl4.text = dict["id"] as? String
cell.lbl5.text = dict["ad_zip"] as? String
let imageUrlString = dict["ad_path"]
let imageUrl:URL = URL(string: imageUrlString as! String)!
let imageData:NSData = NSData(contentsOf: imageUrl)!
cell.img.image = UIImage(data: imageData as Data)
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
{
return 100
}
}