How do I prevent the triangle of doom when accessing JSON Data? - ios

I am traversing some JSON Data where I need to access nested elements. Currently the easiest way is manually traverse the JSON Data as dictionaries like so
typealias JSON = AnyObject
typealias JSONDictionary = Dictionary<String, JSON>
typealias JSONArray = Array<JSON>
if let jsonData = data["preview"] as? JSONDictionary {
if let source: JSON = jsonData["images"] as? JSONArray {
if let images = source[0] as? JSONDictionary {
if let image = images["source"] as? JSONDictionary {
if let url = image["url"] as? String {
self.imageURL = url
getPhoto()
}
}
}
}
}
This seems unsustainable and the code is ugly.
Is there a better way to do this? How can I better traverse JSON data?

Try this using multiple levels seperated by commas:
if let
jsonData = data["preview"] as? JSONDictionary,
source = jsonData["images"] as? JSONArray,
images = source[0] as? JSONDictionary,
image = images["source"] as? JSONDictionary,
url = image["url"] as? String
{
self.imageURL = url
getPhoto()
}

Related

Swift JSON parsing and printing a specified value from an array

Hi I'm trying to get data from a certain JSON API. I can gat a snapshot of all values from the API, which is shown below. But I can't manage to put a specifiek row in a variable. This is the JSON form which I get. I want to print the "Description" value.Can someone help me with this?
And Hier is my code:
func apiRequest() {
let config = URLSessionConfiguration.default
let username = "F44C3FC2-91AF-5FB2-8B3F-70397C0D447D"
let password = "G23#rE9t1#"
let loginString = String(format: "%#:%#", username, password)
let userPasswordData = loginString.data(using: String.Encoding.utf8)
let base64EncodedCredential = userPasswordData?.base64EncodedString()
let authString = "Basic " + (base64EncodedCredential)!
print(authString)
config.httpAdditionalHeaders = ["Authorization" : authString]
let session = URLSession(configuration: config)
var running = false
let url = NSURL(string: "https://start.jamespro.nl/v4/api/json/projects/?limit=10")
let task = session.dataTask(with: url! as URL) {
( data, response, error) in
if let taskHeader = response as? HTTPURLResponse {
print(taskHeader.statusCode)
}
if error != nil {
print("There is an error!!!")
print(error)
} else {
if let content = data {
do {
let array = try JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers) as AnyObject
print(array)
if let items = array["items"] {
if let description = items["Description"] as? [[String:Any]]{
print(description as Any)
}
}
}
catch {
print("Error: Could not get any data")
}
}
}
running = false
}
running = true
task.resume()
while running {
print("waiting...")
sleep(1)
}
}
First of all the array is not an array and not AnyObject, it's a dictionary which is [String:Any] in Swift 3.
let dictionary = try JSONSerialization.jsonObject(with: content) as! [String:Any]
print(dictionary)
I don't know why all tutorials suggest .mutableContainers as option. That might be useful in Objective-C but is completely meaningless in Swift. Omit the parameter.
The object for key itemsis an array of dictionaries (again, the unspecified JSON type in Swift 3 is Any). Use a repeat loop to get all description values and you have to downcast all values of a dictionary from Any to the expected type.
if let items = dictionary["items"] as? [[String:Any]] {
for item in items {
if let description = item["Description"] as? String {
print(description)
}
}
}
Looks like items is an array that needs to be looped through. Here is some sample code, but I want to warn you that this code is not tested for your data.
if let items = array["items"] as? [[String: AnyObject]] {
for item in items {
if let description = item["Description"] as? String{
print("Description: \(description)")
}
}
}
This code above, or some variation of it, should get you on the right track.
use the SwiftyJSON and it would be as easy as json["items"][i].arrayValue as return and array with items Values or json["items"][i]["description"].stringValue to get a string from a row

How do I reference an object within an array within an object in xcode 8?

I'm looking to try and reference all "titles" within this json (link here) in xcode 8. The issue is there's an object and array that need to be referenced (i believe) before I can pull the title data, and I'm not sure how to do that.
So far this is what i've got:
func fetchFeed(){
let urlRequest = URLRequest(url: URL(string: "http://itunes.apple.com/us/rss/topalbums/limit=10/json")!)
let task = URLSession.shared.dataTask(with: urlRequest) { (data,response,error) in
if error != nil {
print(error)
return
}
self.artists = [Artist]()
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as! [String : AnyObject]
if let feedFromJson = json["feed"]?["entry"] as? [[String : AnyObject]] {
for feedFromJson in feedsFromJson {
let feed = Feed()
if let entry = feedFromJson["entry"] as? String, let author = feedFromJson["domain"] as? String {
feed.entry = entry
article.headline = title
}
self.articles?.append(article)
}
}
DispatchQueue.main.async {
self.tableview.reloadData()
And thank you for your help in advance!
I'm working hard to try to understand what you need. If you want to get an Article array where the headline is the title label for the entry, here is how I cheated it out.
func articles(from json: Any?) -> [Article] {
guard let json = json as? NSDictionary, let entries = json.value(forKeyPath: "feed.entry") as? [NSDictionary] else {
return []
}
return entries.flatMap { entry in
guard let title = entry.value(forKeyPath: "title.label") as? String else {
return nil
}
var article = Article()
article.headline = title
return article
}
}
you call it as such
self.articles = articles(from: json)
NSDictionary has the method value(forKeyPath:) that is near magic. Calling json.value(forKeyPath: "feed.entry") returns an array of dictionaries. Each dictionary is an "entry" object in the json. Next, I map each entry to call entry.value(forKeyPath: "title.label") which returns a string.
If this is something more than a quick solution, then I would consider adding SwiftyJSON to your project.
func articles(from json: Any?) -> [Article] {
return JSON(json)["feed"]["entry"].arrayValue.flatMap { entry in
guard let title = entry["title"]["label"].string else {
return nil
}
var article = Article()
article.headline = title
return article
}
}
There is two kinds of titles.
the "feed" and the "entry".
if let entry = feedFromJson["entry"] as? String, let author = feedFromJson["domain"] as? String {
The practice of iOS is not this.
feedFromJson["entry"] is nil ,not a string . I guess you try to get the "feed" title.
if let entry = (json["feed"] as Dictionary)?["title"]
To get the "entry" title. Just traverse the array, and get the title.
let titleDict = feedFromJson["title"] as? Dictionary
let title = titleDict["title"] as? String
article.headline = title
Better to know the structure of the JSON data.
It's too quick.
if let feedFromJson = json["feed"]?["entry"] as? [[String :
AnyObject]] {
You should step by step.
if let feedFromJson = (json["feed"] as Dictionary)?["entry"] as? [[String : AnyObject]] {

swift: Alphabetiz data being parsed from a JSON

I wrote my own function in Swift2 to parse a JSON. Once the JSON is parsed, a list of data that was pulled from the JSON is displayed in a tableView on my app. I am trying to figure out how to display this data in alphabetical order. I think this needs to happen somewhere before the append method I call in the function. I would imagine this needs to be a sort function but I have not been able to figure out the correct sort function in Swift2 that will execute this properly. Any help I can get is appreciated!
Here is my parseJSON function:
func parseJSON(){
do{
let data = NSData(contentsOfURL: NSURL(string: "https://jsonblob.com/api/jsonBlob/580d0ccce4b0bcac9f837fbe")!)
let jsonResult = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers)
for anItem in jsonResult as! [Dictionary<String, AnyObject>]{
let mifiName2 = anItem["name"] as! String
let mifiId = anItem["employeeId"] as! Int
let newName = Name(mifiName: mifiName2, mifiId: mifiId)
nameOfMifi.append(newName)
//print("Name: \(newName)")
}
}
catch let error as NSError{
print(error.debugDescription)
}
}
You need to sort your array after all the object is append in Array means after the for loop.
for anItem in jsonResult as! [Dictionary<String, AnyObject>]{
let mifiName2 = anItem["name"] as! String
let mifiId = anItem["employeeId"] as! Int
let newName = Name(mifiName: mifiName2, mifiId: mifiId)
nameOfMifi.append(newName)
//print("Name: \(newName)")
}
//Now you need to sort your array on the basis of name like this
nameOfMifi.sortInPlace { $0.mifiName < $1.mifiName }
Edit: As #vadian suggested do not use NSData(contentsOfURL:) because it will block your UI, so batter to use NSURLSession like this.
let session = NSURLSession.sharedSession()
let url = NSURL(string: "https://jsonblob.com/api/jsonBlob/580d0ccce4b0bcac9f837fbe")!
var task = session.dataTaskWithURL(url, completionHandler: {
(data, response, error) -> Void in
if error != nil {
return
}
if let jsonResult = try? NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as? [Dictionary<String, AnyObject>] {
for anItem in jsonResult {
let mifiName2 = anItem["name"] as! String
let mifiId = anItem["employeeId"] as! Int
let newName = Name(mifiName: mifiName2, mifiId: mifiId)
nameOfMifi.append(newName)
//print("Name: \(newName)")
}
//Now you need to sort your array on the basis of name like this
nameOfMifi.sortInPlace { $0.mifiName < $1.mifiName }
//Now reload tableView on main thread.
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}
}
})
task.resume()

How to get all keys and values into separate String arrays from NSDictionary in Swift?

let urlAsString = "https://drive.google.com/uc?export=download&id=0B2bvUUCDODywWTV2Q2IwVjFaLW8"
let url = NSURL(string: urlAsString)!
let urlSession = NSURLSession.sharedSession()
let jsonQuery = urlSession.dataTaskWithURL(url, completionHandler: { data, response, error -> Void in
do {
if let jsonDate = data, let jsonResult = try NSJSONSerialization.JSONObjectWithData(jsonDate, options: []) as? NSDictionary {
print(jsonResult)
}
} catch let error as NSError {
print(error)
}
})
jsonQuery.resume()
Okay so here i am receiving data from online json then storing it as NSDictionary in jsonresult . I need to get all keys and values as into two separate arrays ?
Basically i want this
jsonresult.allkeys --> String array
jsonresult.allvalues --> String array
You can use:
let keys = jsonResult.flatMap(){ $0.0 as? String }
let values = jsonResult.flatMap(){ $0.1 }
It is quite simple because you are using jsonResult as NSDictionary.
let dict: NSDictionary = ["Key1" : "Value1", "Key2" : "Value2"]
let keys = dict.allKeys
let values = dict.allValues
In you're case
let keys:[String] = dict.allKeys as! [String]
var values:[String]
if let valuesSting = dict.allValues as? [String] {
values = valuesSting
}
For anyone trying it with newer version Swift please use compactMap()instead of flatMap()
let keys = jsonResult.compactMap(){ $0.0 as? String }
let values = jsonResult.compactMap(){ $0.1 }

Json parsing in iOS playground do method is not parsing

I am trying to parse some json data into three different arrays based off the label in the json. I seem to be stuck and don't know why my for loop is never being entered. I am new to iOS and am using this to learn swift. Any help will be appreciated.
Here is the code that I am using:
var myPicture = [String]()
var myPath = [String]()
var mylabel = [String]()
let jsonString = "[{\"picture\" : \"Picture 1 \", \"path\": \"Path 1\" , \"label\" : \"Label 1\"}]"
//Convert jsonString to NSData
let myData = jsonString.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
do{
let promoJson = try NSJSONSerialization.JSONObjectWithData(myData, options:.AllowFragments)
if let promtions = promoJson[""] as? [[String: AnyObject]] {
for promtions in promtions {
if let picture = promtions["picture"] as? String
{
myPicture.append(picture)
if let path = promtions["path"] as? String
{
myPath.append(path)
if let label = promtions["label"] as? String
{
mylabel.append(label)
}
}
}
}
}
}catch {
print("Error with Json: \(error)")
}
print(myPicture.first)
print(myPath.first)
print(mylabel.first)
The results for the print are all nil. So nothing is being appended to the arrays
The if let promtions = promoJson[""] part won't work and would be useless anyway. This is only promoJson that you have to cast to an array of dictionaries.
You weren't that far from the solution, look at my working version of your code:
do {
let promoJson = try NSJSONSerialization.JSONObjectWithData(myData, options: [])
if let promtions = promoJson as? [[String: AnyObject]] {
for promtion in promtions {
if let picture = promtion["picture"] as? String {
myPicture.append(picture)
}
if let path = promtion["path"] as? String {
myPath.append(path)
}
if let label = promtion["label"] as? String {
mylabel.append(label)
}
}
}
} catch let error as NSError {
print(error.debugDescription)
}
Alternative
Now that the issue is resolved, let me suggest you another way: instead of separate arrays for your data, use one array of objects holding your data.
For example, make a struct like this:
struct Promotion {
let picture: String
let path: String
let label: String
}
And an array for instances of this struct:
var myPromotions = [Promotion]()
Now we can decode the JSON, create objects from it then store them in the array:
do {
let promoJson = try NSJSONSerialization.JSONObjectWithData(myData, options: [])
if let promtions = promoJson as? [[String: AnyObject]] {
for promtion in promtions {
if let picture = promtion["picture"] as? String,
path = promtion["path"] as? String,
label = promtion["label"] as? String {
let promo = Promotion(picture: picture, path: path, label: label)
myPromotions.append(promo)
}
}
}
} catch let error as NSError {
print(error.debugDescription)
}
Now look at the content of the array, very convenient:
for promo in myPromotions {
print(promo.label)
print(promo.path)
print(promo.picture)
}
When you are converting it is already an array.
import Foundation
import UIKit
var myPicture = [String]()
var myPath = [String]()
var mylabel = [String]()
let jsonString = "[{\"picture\" : \"Picture 1 \", \"path\": \"Path 1\" , \"label\" : \"Label 1\"}]"
//Convert jsonString to NSData
let myData = jsonString.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
do{
let promoJson = try NSJSONSerialization.JSONObjectWithData(myData, options:.AllowFragments) as! NSArray
for promtions in promoJson {
if let picture = promtions["picture"] as? String
{
myPicture.append(picture)
if let path = promtions["path"] as? String
{
myPath.append(path)
if let label = promtions["label"] as? String
{
mylabel.append(label)
}
}
}
}
}catch
{
print("Error with Json: \(error)")
}
print(myPicture.first) // "Optional("Picture 1 ")\n"
print(myPath.first) // "Optional("Path 1")\n"
print(mylabel.first) // "Optional("Label 1")\n"
This does the job.

Resources