parsing a JSON array in Swift - json

I have this working but it seems like a very manual process and I can't work out how to loop inside a loop (or if I should). Right now I am just testing this with 3 variables, but there will ultimately be about 100. Here's my playground. Is there a way to simplify this so I don't have to manually add each array name?
import Foundation
var json_data_url = "216.92.214.107/data_test.json"
var LRKSFOweekdayDep : [String] = [String]()
var LRKSFOweekendDep : [String] = [String]()
var SFOLRKweekdayDep : [String] = [String]()
let journeysURL:NSURL = NSURL(string: json_data_url)!
let data = NSData(contentsOfURL: journeysURL)!
do {
let json = try NSJSONSerialization.JSONObjectWithData(data, options: .AllowFragments)
print(json)
if let dep_obj = json as? NSDictionary {
if let array_journey = dep_obj["journey"] as? NSArray{
if let journies = array_journey[0] as? NSDictionary {
if let array_dep = journies["LRKSFOweekdayDep"] as? NSDictionary{
if let dep = array_dep["dep"] as? NSArray {
for var i = 0; i < dep.count; ++i
{
let add = dep[i] as! String
LRKSFOweekdayDep.append(add)
}
print(LRKSFOweekdayDep)
}
}
}
if let journies = array_journey[1] as? NSDictionary {
if let array_dep = journies["LRKSFOweekendDep"] as? NSDictionary{
if let dep = array_dep["dep"] as? NSArray {
for var i = 0; i < dep.count; ++i
{
let add = dep[i] as! String
LRKSFOweekendDep.append(add)
}
print(LRKSFOweekendDep)
}
}
}
if let journies = array_journey[2] as? NSDictionary {
if let array_dep = journies["SFOLRKweekdayDep"] as? NSDictionary{
if let dep = array_dep["dep"] as? NSArray {
for var i = 0; i < dep.count; ++i
{
let add = dep[i] as! String
SFOLRKweekdayDep.append(add)
}
print(SFOLRKweekdayDep)
}
}
}
}
}
} catch {
print("error serializing JSON: \(error)")
}

You might want to look at using SwiftyJSON to make the parsing easier.

Right now, you have something like:
if let dep = array_dep["dep"] as? NSArray {
for var i = 0; i < dep.count; ++i {
let add = dep[i] as! String
LRKSFOweekendDep.append(add)
}
}
That can be simplified to:
LRKSFOweekendDep = array_dep["dep"] as? [String]
That assumes of course, that you define LRKSFOweekendDep to be optional. If it's not optional, you can do:
LRKSFOweekendDep = array_dep["dep"] as? [String] ?? []
But, it should be optional.
In a comment, you say that there are going to be 100 of these. Rather than having a variable for each, I would have thought that you'd rather keep an array of objects. For example, consider:
struct Journey {
let name: String
let departures: [String]
}
Then, to parse your JSON, you could iterate through the results:
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: [])
var journeys = [Journey]()
if let results = json as? [String: AnyObject], let array = results["journey"] as? [[String: AnyObject]] {
for dictionary in array {
for (name, departures) in dictionary {
if let departureDictionary = departures as? [String: [AnyObject]], let departureList = departureDictionary["dep"] as? [String] {
journeys.append(Journey(name: name, departures: departureList))
}
}
}
}
Finally, I would advise against NSData(contentsOfURL:), because that's synchronous. Use NSURLSession's dataTaskWithURL, which is asynchronous. Also, if you use data! pattern, first check to make sure it's not nil. Otherwise, if data was nil for any reason outside of your control (e.g. the web server is down, internet is temporarily interrupted, etc.), the app will crash rather than handling it gracefully.
Putting that all together, you get something like:
func retrieveJourneys(completionHandler: ([Journey]?, NSError?) -> ()) {
let task = NSURLSession.sharedSession().dataTaskWithURL(journeysURL) { data, response, error in
guard error == nil && data != nil else {
completionHandler(nil, error)
return
}
var json: [String: AnyObject]?
do {
json = try NSJSONSerialization.JSONObjectWithData(data!, options: []) as? [String: AnyObject]
} catch let parseError as NSError {
completionHandler(nil, parseError)
}
var journeys = [Journey]()
if let array = json!["journey"] as? [[String: AnyObject]] {
for dictionary in array {
for (name, departures) in dictionary {
if let departureDictionary = departures as? [String: [AnyObject]], let departureList = departureDictionary["dep"] as? [String] {
journeys.append(Journey(name: name, departures: departureList))
}
}
}
}
completionHandler(journeys, nil)
}
task.resume()
}
And then you'd use it like so:
var journeys: [Journey]?
override func viewDidLoad() {
super.viewDidLoad()
retrieveJourneys { journeys, error in
guard error == nil && journeys != nil else { // make sure it didn't have network problem
print(error)
return
}
dispatch_async(dispatch_get_main_queue()) { // now update model on main queue
self.journeys = journeys
// and, for giggles and grins, this is how you might grab the first one and examine it:
let someJourney = self.journeys![0]
print(someJourney.name)
print(someJourney.departures)
}
}
}
Now, the above assumes that you wanted an ordered list of journeys, sorted by the order you received them.
On the other hand, if you didn't care about the order, but wanted an efficient way to retrieve the departures associated with a given key, you might use a dictionary, instead:
func retrieveDepartures(completionHandler: ([String: [String]]?, NSError?) -> ()) {
let task = NSURLSession.sharedSession().dataTaskWithURL(journeysURL) { data, response, error in
guard error == nil && data != nil else {
completionHandler(nil, error)
return
}
var json: [String: AnyObject]?
do {
json = try NSJSONSerialization.JSONObjectWithData(data!, options: []) as? [String: AnyObject]
} catch let parseError as NSError {
completionHandler(nil, parseError)
}
var departures = [String: [String]]()
if let array = json!["journey"] as? [[String: AnyObject]] {
for dictionary in array {
for (name, departureObject) in dictionary {
if let departureDictionary = departureObject as? [String: [AnyObject]], let departureList = departureDictionary["dep"] as? [String] {
departures[name] = departureList
}
}
}
}
completionHandler(departures, nil)
}
task.resume()
}
And then:
var departures: [String: [String]]?
override func viewDidLoad() {
super.viewDidLoad()
retrieveDepartures { departures, error in
guard error == nil && departures != nil else {
print(error)
return
}
dispatch_async(dispatch_get_main_queue()) {
self.departures = departures
// and, for giggles and grins, this is how you might grab a list of departures given a particular key
let departureTimes = self.departures!["LRKSFOweekdayDep"]
print(departureTimes)
}
}
}

Related

Trouble getting data from TheMovieDB API with Swift JSON

I am brand new to using JSON and wanted to get started with a simple app to provide a movie overview when you type in a title. My below code returns everything in one big string. How do I get just one piece of information like the overview or year?
With my below attempt, print(obj["overview"] as Any)) prints "nil" and print(obj) looks like this:
{
page = 1;
results = (
{
adult = 0;
"backdrop_path" = "/A0aGxrCGRBuCrDltGYiKGeAUect.jpg";
"genre_ids" = (
53,
80
);
id = 680;
"original_language" = en;
"original_title" = "Pulp Fiction";
overview = "A burger-loving hit man, his philosophical partner, a drug-addled gangster's moll and a washed-up boxer converge in this sprawling, comedic crime caper. Their adventures unfurl in three stories that ingeniously trip back and forth in time.";
Current Code:
let query = "Pulp+Fiction"
let urlString = "https://api.themoviedb.org/3/search/movie?api_key={MYAPIKEY}&query=\(query)"
let url = URL(string: urlString)
URLSession.shared.dataTask(with:url!) { (data, response, error) in
if error != nil {
print(error as Any)
} else {
do {
let parsedData = try JSONSerialization.jsonObject(with: data!) as Any
if let obj = parsedData as? NSDictionary {
print(obj["overview"] as Any)
print(obj)
}
} catch {
print("error")
} }
}.resume()
}
// write this extension anywhere in your any swift file
extension String{
func toDictionary() -> NSDictionary {
let blankDict : NSDictionary = [:]
if let data = self.data(using: .utf8) {
do {
return try JSONSerialization.jsonObject(with: data, options: []) as! NSDictionary
} catch {
print(error.localizedDescription)
}
}
return blankDict
}
}
//now in your code modify as
if data != nil {
let responseString = String(data: data!, encoding: .utf8)!
if(responseString != "")
{
//convert response string into dictionary using extended method
let responseValues = responseString.toDictionary()
//access value using keyPath using
let value = responseValues.value(forKeyPath: "key.key2")
//where key2 is the target key which is inside the value of key
}
}
First of all JSON results are never Any. As already mentioned in the comments the root object is a dictionary
if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [String:Any],
The key overview is in array for key results
let results = parsedData["results"] as? [[String:Any]] {
You have to iterate over the array to get the values for key overview
for result in results {
print(result["overview"] as? String ?? "no value for key overview")
}
}
It's highly recommended to use the Codable protocol and custom structs in Swift 4.

Accessing Object Attributes from JSON, Swift3

let url = URL(string: "http://192.168.30.243:5000/trippy/destination/info?id=4864cc0a-8")
let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error != nil {
print ("ERROR")
}
else {
if let content = data {
do {
//Array
let myJson = try JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers) as AnyObject
print(myJson)
if let information = myJson as? NSDictionary {
print (information.value(forKey: "EmergencyNumbers")!)
if let number = information.value(forKey: "EmergencyNumbers") as? NSArray {
//This is the part I am unsure about
if let description = number[0] as? AnyObject {
//I know do not know how to access the object's attribute values
}
}
}
}
catch {
}
}
}
}
task.resume()
}
I have used JSON to parse data from the web. I have utilized a dictionary to access the information and then an array to get the data from the certain key. Within this array are lie some objects. How do I access each of these objects' properties' values?
JSON Example:
{
Currency = testCurrency;
DestinationId = "4864cc0a-8";
DialCode = testDialCode;
DoesntUseMetricSystem = 0;
DrinkingAge = 16;
DriverLicense = 1;
EmergencyNumbers = (
{
Description = "Emergency Pizza Delivery";
Id = 1;
Number = 6969;
}
);
Id = 1;
IsNorthHemisphere = 1;
OfficialLanguage = {
Id = 1;
Name = testLanguage;
};
PowerGridVoltage = 226;
PowerSocket = dk;
Telecoms = nonern;
Tipping = 2;
WidelySpokenLanguages = (
{
Id = 2;
Name = testtLanguage;
}
);
WrongSideOfRoad = 0;
}
I see you are coming from Objective-C world, so first I'd recommend you give up using NSArray, NSDictionary etc. in favor of their Swift counterparts Array and Dictionary:
let task = URLSession.shared.dataTask(with: url!) { data, response, error in
...
let JSON = try? JSONSerialization.jsonObject(with: data!, options: [])
if let dictionary = JSON as? [String: Any],
let emergencyNumbers = dictionary["EmergencyNumbers"] as? [[String: Any]]
{
emergencyNumbers.forEach { numbers in
print(numbers["Description"] as? String)
print(numbers["Id"] as? Int)
print(numbers["Number"] as? Int)
}
}
}
By the way [String: Any] is just a syntactic sugar for Dictionary<String, Any>. Same applies to arrays as well: [[String: Any]] is for Array<Dictionary<String, Any>>.
As always, don't use NSArray / NSDictionary in Swift. You throw away the type information.
The root object is a dictionary ([String:Any]), the value for key EmergencyNumbers is an array ([[String:Any]]). Use a loop to iterate thru the array.
if let root = try JSONSerialization.jsonObject(with: content) as? [String:Any] {
print(myJson)
if let emergencyNumbers = root["EmergencyNumbers"] as? [[String:Any]] {
for emergencyNumber in emergencyNumbers {
let description = emergencyNumber["Description"] as? String
let id = emergencyNumber["Id"] as? Int
let number = emergencyNumber["Number"] as? Int
print("description", description ?? "n/a")
print("id", id ?? "n/a")
print("number", number ?? "n/a")
}
}
Some other bad habits:
.mutableContainers is completely meaningless in Swift. The hilarious thing is, everybody who passes the option .mutableContainers assigns the result to an immutable constant.
The unspecified JSON type in Swift 3+ is Any not AnyObject
valueForKey, a KVC method, is inappropriate for this purpose, use objectForKey or key subscription. With Swift native types don't use it at all.

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 : football-data API doesn't work

I'm trying to use football-data.org api. I wrote some code same with before I did sample. But this api is using token and I didn't figured out how to add and do that.
I did these code and nothing happens :
func getData(){
let url = NSMutableURLRequest(URL: NSURL(string: "http://api.football-data.org/v1/soccerseasons/424/fixtures"))
url.addValue("my token is here", forHTTPHeaderField: "X-Auth-Token")
url.HTTPMethod = "GET"
let task = NSURLSession.sharedSession().dataTaskWithRequest(url) { (data, response, error) in
self.setLabels(data!)
}
task.resume()
}
func setLabels(MatchData: NSData){
//var jsonError: NSError?
do{
let json = try NSJSONSerialization.JSONObjectWithData(MatchData, options: NSJSONReadingOptions.MutableContainers) as! NSDictionary
if let sys = json["soccerseason"] as? NSDictionary{
if (sys["href"] as? String) != nil{
let seasonsUrl = sys["href"] as! String
print(seasonsUrl)
}
}
}
catch{
//error
}
}
I'm not getting value or something. I'm new with json. What's wrong in my code?
"soccerseason" is in "_links", like this:
if let links = json["_links"] as? [String:AnyObject],
sys = links["soccerseason"] as? [String:String],
seasonsUrl = sys["href"] {
print(seasonsUrl)
}
This is for the main one.
I've also noticed there's one (the same one, actually) in each dictionary in the main array:
if let fixtures = json["fixtures"] as? [[String:AnyObject]] {
for fixture in fixtures {
if let links = fixture["_links"] as? [String:AnyObject],
sys = links["soccerseason"] as? [String:String],
seasonsUrl = sys["href"] {
print(seasonsUrl)
}
}
}
The URLs are in the _links part in each dictionary in the fixtures array:
if let fixtures = json["fixtures"] as? [[String:AnyObject]] {
for fixture in fixtures {
if let links = fixture["_links"] as? [String:[String:String]],
season = links["soccerseason"],
seasonsUrl = season["href"],
awayTeam = links["awayTeam"],
awayTeamUrl = awayTeam["href"] {
print(seasonsUrl)
print(awayTeamUrl)
}
}
}
And awayTeamName and homeTeamName are at the same level than _links inside the array of dictionaries:
if let fixtures = json["fixtures"] as? [[String:AnyObject]] {
for fixture in fixtures {
if let awayTeamName = fixture["awayTeamName"] as? String,
homeTeamName = fixture["homeTeamName"] as? String {
print(awayTeamName)
print(homeTeamName)
}
}
}

Asynchronous Issue JSON Swift

let task = session.dataTaskWithURL(url!, completionHandler: {
data, response, error -> Void in
if (error != nil) {
println(error)
} else {
let jsonresult = NSJSONSerialization.JSONObjectWithData(data, options:NSJSONReadingOptions.MutableContainers, error: nil) as NSDictionary
var dummyfeed:AnyObject
//println(jsonresult)
for var i = 0; i < jsonresult["feed"]!.count; i++ {
self.feeds.append([String:String]())
dummyfeed = jsonresult["feed"]![i] as NSDictionary
self.feeds[i]["id"] = dummyfeed["id"] as? String
self.feeds[i]["name"] = dummyfeed["name"] as? String
self.feeds[i]["status"] = dummyfeed["status"] as? String
self.feeds[i]["profilePic"] = dummyfeed["profilePic"] as? String
self.feeds[i]["timeStamp"] = dummyfeed["timeStamp"] as? String
self.feeds[i]["url"] = dummyfeed["url"] as? String
}
}
})
task.resume()
So Feeds is a global variable, so that I display the picture of each entry in Feeds on a table view. But it's calling asynchronously println(self.feeds) inside the task variable and println(feeds) outside of the task variable are differnent. How do I make it synchronously?
Do not make it run synchronously. Run it asynchronously, but then synchronize the interaction with feeds. The simplest way to achieve that it to dispatch the updating of the feeds back to the main queue and reloadData for the table view. This eliminates the possibility that you'll be using it from the main queue while it's mutating in the background, but avoids the horrible UX of doing this network request synchronously:
let task = session.dataTaskWithURL(url!) { data, response, error in
if (error != nil) {
println(error)
} else {
var parseError: NSError?
if let jsonresult = NSJSONSerialization.JSONObjectWithData(data, options:nil, error: nil) as? NSDictionary {
if let receivedFeeds = jsonresult["feed"] as? [[String: AnyObject]] {
dispatch_async(dispatch_get_main_queue()) {
self.feeds = [[String: String]]()
for receivedFeed in receivedFeeds {
var outputFeed = [String : String]()
outputFeed["id"] = receivedFeed["id"] as? String
outputFeed["name"] = receivedFeed["name"] as? String
outputFeed["status"] = receivedFeed["status"] as? String
outputFeed["profilePic"] = receivedFeed["profilePic"] as? String
outputFeed["timeStamp"] = receivedFeed["timeStamp"] as? String
outputFeed["url"] = receivedFeed["url"] as? String
self.feeds.append(outputFeed)
}
self.tableView.reloadData()
}
} else {
println("did not find `feed`")
}
} else {
println("problem parsing JSON: \(parseError)")
}
}
}
task.resume()
That should be a little more robust handling errors and employs asynchronous pattern of letting request run asynchronously, but dispatch updating of model object and UI back to the main thread.
let task = session.dataTaskWithURL(url!, completionHandler: {
data, response, error -> Void in
if (error != nil) {
println(error)
} else {
let jsonresult = NSJSONSerialization.JSONObjectWithData(data, options:NSJSONReadingOptions.MutableContainers, error: nil) as NSDictionary
var dummyfeed:AnyObject
//println(jsonresult)
for var i = 0; i < jsonresult["feed"]!.count; i++ {
self.feeds.append([String:String]())
dummyfeed = jsonresult["feed"]![i] as NSDictionary
dispatch_async(dispatch_get_main_queue()) {
self.feeds[i]["id"] = dummyfeed["id"] as? String
self.feeds[i]["name"] = dummyfeed["name"] as? String
self.feeds[i]["status"] = dummyfeed["status"] as? String
self.feeds[i]["profilePic"] = dummyfeed["profilePic"] as? String
self.feeds[i]["timeStamp"] = dummyfeed["timeStamp"] as? String
self.feeds[i]["url"] = dummyfeed["url"] as? String
}
}
self.tableView.reloadData()
}
})
task.resume()
Hey Rob, I did what I think you tell me to do, and feeds is still empty :(
I have same problem my code is was working fine, but now, using dataTaskWithURL it didn't return any data, or even error. I think issue is iOS 8.2 I upgraded.