Error when try to parse json? - json

I'm trying to parse JSON, but it's not working. I want to get id from the JSON at the URL, but it shows me null value.
var names = [String]()
var SearchURL = "http://ios.khothe.vn/web/gamecards/authenticate/user/dungphiau/pass/829d81d46bad96825dc52a6e1675aab0"
typealias jsonStandard = [String : AnyObject]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
callAlamo(url: SearchURL)
}
func callAlamo(url : String) {
Alamofire.request(url).responseJSON(completionHandler:
{
responds in
self.parseData(JsonData: responds.data!)
})
}
func parseData(JsonData : Data) {
do{
var readableJson = try JSONSerialization.jsonObject(with: JsonData, options: .mutableContainers) as! jsonStandard
let tracks = readableJson["id"] as? jsonStandard
print(tracks)
} catch{
print(error)
}
}

The value for key id is String not [String:AnyObject] aka jsonStandard
let tracks = readableJson["id"] as? String
Consider that in Swift 3 the type of a JSON dictionary is [String:Any]
typealias jsonStandard = [String : Any]

Related

Problems with Alamofire response handler

For my project I want to parse an Alamofire JSON response and save it in Realm. I already tested this by fetching the JSON from a Mock-API and it worked fine, but when I'm trying to incorporate my Code into my AM-response handling I'm getting this error:
Invalid conversion from throwing function of type '(AFDataResponse) throws -> Void' (aka '(DataResponse<Any, AFError>) throws -> ()') to non-throwing function type '(AFDataResponse) -> Void' (aka '(DataResponse<Any, AFError>) -> ()')
The Code for the response looks like this:
.responseJSON { response in //this is where I'm getting the error
print(response)
self.books = [Books]()
do {
if 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)
}
This is my AM-Request as a whole:
public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let image:UIImage = info[UIImagePickerController.InfoKey.editedImage] as? UIImage {
self.myImage = image
AF.upload(multipartFormData: { (multipartFormData) in
multipartFormData.append(self.myImage.jpegData(compressionQuality: 0.5)!, withName: "image", fileName: "image.png", mimeType: "image/jpeg")
}, to: "https://booknerdvirtualreadinglist.herokuapp.com/getbook" , headers: nil )
.uploadProgress { progress in
print(progress)
}
.responseJSON { response in
print(response)
self.books = [Books]()
do {
if 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)
}
dismiss(animated: true, completion: nil)
}
Thank you in advance!
You are not catching the error correctly because your "try catch" only has a do block.
This is how you can properly handle an error in Swift:
do {
// some throwing code
} catch {
print("Failed with error \(error)")
}
The catch-part is missing.

Reading local JSON file and using it to populate UITableView

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.

How to properly create objects from a JSON dictionary that start with a numbers instead of strings?

I'm having a tough time creating objects from the JSON below. It's weirdly formatted with = and ; but that's how it looks when printed to console:
result = (
{
media = {
image = {
1000 = "/assets/img/cities/basel-switzerland-1000px.jpg";
1500 = "/assets/img/cities/basel-switzerland-1500px.jpg";
250 = "/assets/img/cities/basel-switzerland-250px.jpg";
500 = "/assets/img/cities/basel-switzerland-500px.jpg";
};
};
}
)
I've created custom objects but I keep getting a
EXC_BAD_INSTRUCTION
error when I use [Int: Any] for the "image" and when I substitute NSNumber instead I get an error:
Could not cast value of type 'NSTaggedPointerString' (0x10ee4ad10) to
'NSNumber' (0x10bc88488).
Here is my custom class for the JSON objects:
class sampleJSON {
var mediaDictionary: [String: Any]
var imageDictionary: [Int: Any]
var image: URL
init( mediaDictionary: [String: Any], imageDictionary: [Int: Any], image: URL){
self.mediaDictionary = mediaDictionary
self.imageDictionary = imageDictionary
self.image = image
}
init(resultsDictionary:[String: Any]){
mediaDictionary = (resultsDictionary["media"] as? [String: Any])!
imageDictionary = (mediaDictionary["image"] as? [Int: Any])!
image = URL(string: imageDictionary[1000] as! String)!
}
This is how I'm parsing the JSON data:
static func downloadAllData(urlExtension: String, completionHandler: #escaping (sampleJSON?) -> ()) {
let usm = UrlSessionNetworkManager.sharedManager
if let jsonDictionary = usm.parseJSONFromData(urlExtension:"\(urlExtension)")
{
let resultDictionaries = jsonDictionary["result"] as! [[String : Any]]
for resultsDictionary in resultDictionaries {// enumerate through dictionary
let nomadInfo = sampleJSON(resultsDictionary: resultsDictionary)
print(nomadInfo.mediaDictionary)
completionHandler(nomadInfo)
}
} else {
print("Error: Cannot retrieve JSON Data")
}
}
}
Replace all occurences of [Int: Any] with [String: Any], and make that change in the server code, too. As mentioned by #Paulw11, JSON keys can only be strings (but a value can be a string in double quotes, or a number, or true or false or null, or an object or an array). Also, as I mentioned, never use (a as? b)!, instead use a as! b.
The new (and now valid) JSON should look like:
result = (
{
media = {
image = {
"1000" = "/assets/img/cities/basel-switzerland-1000px.jpg";
"1500" = "/assets/img/cities/basel-switzerland-1500px.jpg";
"250" = "/assets/img/cities/basel-switzerland-250px.jpg";
"500" = "/assets/img/cities/basel-switzerland-500px.jpg";
};
};
}
)
The custom class should be:
class sampleJSON {
var mediaDictionary: [String: Any]
var imageDictionary: [String: Any]
var image: URL
init( mediaDictionary: [String: Any], imageDictionary: [String: Any], image: URL){
self.mediaDictionary = mediaDictionary
self.imageDictionary = imageDictionary
self.image = image
}
init(resultsDictionary:[String: Any]){
mediaDictionary = resultsDictionary["media"] as! [String: Any]
imageDictionary = mediaDictionary["image"] as! [String: Any]
image = URL(string: imageDictionary["1000"] as! String)!
}
And the parsing function should be (just a small, unrelated issue fixed):
static func downloadAllData(urlExtension: String, completionHandler: #escaping (sampleJSON?) -> ()) {
let usm = UrlSessionNetworkManager.sharedManager
if let jsonDictionary = usm.parseJSONFromData(urlExtension: urlExtension)
{
let resultDictionaries = jsonDictionary["result"] as! [[String : Any]]
for resultsDictionary in resultDictionaries {// enumerate through dictionary
let nomadInfo = sampleJSON(resultsDictionary: resultsDictionary)
print(nomadInfo.mediaDictionary)
completionHandler(nomadInfo)
}
} else {
print("Error: Cannot retrieve JSON Data")
}
}
}

Struct Init with JSON and flatMap

I'm having a problem with the following code. I'm downloading a list of actors in JSON and I want to populate Struct Actor with the received data. Everything works great until I try to flatMap on the received data and try to initialize the struct Actor. When I try to compile the code i get the error: Cannot assign value of type '()' to type [Actor]. The error corresponds to a line in viewDidLoad actorsList = downloadActors() Would anybody have any recommendation who to solve this?
import UIKit
func downloadActors() {
var request = URLRequest(url: URL(string: "url...")!)
request.httpMethod = "POST"
let postString = "actorGroup=\("Superhero")"
request.httpBody = postString.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
DispatchQueue.main.async {
guard let data = data, error == nil else {
print("error=\(error)")
return
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 {
print("error : statusCode should be 200 but is \(httpStatus.statusCode)")
print("response = \(response)")
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode == 200 {
do {
let json = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers) as? [String: AnyObject]
guard let actorsJSON = json?["response"] as? [[String : AnyObject]] else {
return
}
} catch {
print("catch error")
}
}
}
}
task.resume()
}
func loadActors() -> [Actor] {
if let actors = actorsJSON as? [[String : AnyObject]] {
return actors.flatMap(Actor.init)
}
}
let actorsArray = loadActors()
class MasterViewController: UITableViewController {
var actorsList = [Actor]()
var detailViewController: DetailViewController? = nil
var objects = [Any]()
override func viewDidLoad() {
super.viewDidLoad()
actorsList = downloadActors()
print(actorsList)
Struct Actors is as follows:
struct Job {
let actorGroup: String
let actorName: String
}
extension Actor: JSONDecodable {
init?(JSON: [String : AnyObject]) {
guard let actorGroup = JSON["actorGroup"] as? String, let actorName = JSON["actorName"] as? String else {
return nil
}
self. actorGroup = actorGroup
self. actorName = actorName
}
}
let listActors = actorsJSON as? [[String : AnyObject]] {
Should be:
if let listActors = actorsJSON as? [[String : AnyObject]] {
Edit: For more info I'd like to add Vadian's comment:
Very confusing code. What does the function in the middle of the do block? Why do you type-check actorsJSON twice? The computed property is let listActors... which should be probably an optional binding (if let ... ). Further .mutableContainers is completely nonsense in Swift. And finally a JSON dictionary is [String:Any] in Swift 3.

SWIFT 2: Loop through JSON array

I am getting this json from a url, the return JSON is:
[{"id":1,"name":"Mary"},{"id":2,"name":"John"}]
I want to display the names in a TableView on IOS.
My Swift2 Code is:
class ViewController: UIViewController, UITableViewDelegate {
var NumberOfPersons = 0
var NameOfPerson = [String]()
override func viewDidLoad() {
super.viewDidLoad()
parseJSON()
}
func parseJSON(){
do {
let data = NSData(contentsOfURL: NSURL(string: "http://zzzzzz.com/API/name.php")!)
let jsonResult = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers)
let NumberOfPersons = jsonResult.count
**LOOP THROUGH THE JSON ARRAY**
} catch let error as NSError {
print(error)
}
}
}
How can I loop through the JSON array to put which name in a cell on a Table View?
The variable jsonResult is an array of dictionaries, so you can loop through the array with
for anItem in jsonResult as! [Dictionary<String, AnyObject>] { // or [[String:AnyObject]]
let personName = anItem["name"] as! String
let personID = anItem["id"] as! Int
// do something with personName and personID
}
In Swift 3 the unspecified JSON type has been changed to Any
for anItem in jsonResult as! [Dictionary<String, Any>] { ... // or [[String:Any]]
make the JSON results in a DICT and get it with a loop "for (key, value)"
If your is finally
let jsonResult = [{"id":1,"name":"Mary"},{"id":2,"name":"John"}]
var jsonDictResult[String: Int] = jsonResult;
Updated:
let jsonResult: AnyObject? = NSJSONSerialization.JSONObjectWithData(data,
options: NSJSONReadingOptions.AllowFragments,
error:&parseError)
Updated:
Make the JSON results in a DICT and get it with a loop "for (key, value)"
let jsonResult: AnyObject? = NSJSONSerialization.JSONObjectWithData(data,
options: NSJSONReadingOptions.AllowFragments,
error:&parseError)