Parse JSON Swift 3 Dictionary - json

I am attempting to try to get all 31 teams from the NHL from this JSON link. Here is a look at what the file looks like:
{
"sports":[
{
"name":"hockey",
"slug":"hockey",
"id":70,
"uid":"s:70",
"leagues":[
{
"name":"National Hockey League",
"slug":"nhl",
"abbreviation":"nhl",
"id":90,
"uid":"s:70~l:90",
"groupId":9,
"shortName":"NHL",
"teams":[
{
...team info....
}......
I currently have this do statement in function trying to loop thru all 31 entries in the "teams" array:
if let parsedData = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary {
if let entries: NSArray = parsedData["sports"] as! NSArray {
for entry in entries {
//work with data
}
}
}
I know I have to dig a bit deeper on the "if let entries" line, but I can't seem to get the data I want. Thanks in advance.

First of all, why do so many tutorials suggest / people use .mutableContainers although the object is never going to be mutated and ironically the result is mostly assigned to an immutable object?
Don't do that. Pass no options by omitting the parameter.
Second of all don't fight Swift's strong type system. Use native collection types Array and Dictionary and do not annotate types the compiler can infer.
Let`s create a type alias for convenience:
typealias JSONDictionary = [String:Any]
if let parsedData = try JSONSerialization.jsonObject(with: data!) as? JSONDictionary,
The value for key sports is an array (represented by [])
let sports = parsedData["sports"] as? [JSONDictionary] {
Enumerate the array and get the value for key leagues which is also an array
for sport in sports {
print("sport ", sport["name"] as? String ?? "n/a")
if let leagues = sport["leagues"] as? [JSONDictionary] {
Do the same with leagues and get the teams
for league in leagues {
print("league ", league["name"] as? String ?? "n/a")
if let teams = league["teams"] as? [JSONDictionary] {
for team in teams {
Parsing JSON is pretty easy, there are only two collection types and four value types.
}
}
}
}
}
}

Related

JSON Parsing using Decodable protocol

I have json below for which I want to parse/assign values from
{
"Rooms":[
{
"id":"100",
"title":"CS Classroom",
"description":"Classroom for Computer science students",
"capacity":"50"
},
{
"id":"101",
"title":"Mechanical Lab",
"description":"Mechanical Lab work",
"capacity":"50"
},
{
"id":"108",
"title":"Computer Lab",
"description":"Computer Lab work",
"capacity":"50"
}
]
}
This json is of type [Dictionary: Dictonary] which has only key "Rooms"
While creating struct should I create
struct RoomsInfo: Decodable {
let rooms: Rooms
}
struct Rooms {
let id: String
let title: String
let description: String
let capacity: String
}
My 1st Question is: Since I have only Rooms key , Is there a possiblity to create just one struct instead of two ?
My 2nd Question is: What if my json has keys as "Rooms1", "Rooms2", "Rooms3", "Rooms4"... in this case can i create structure which confirms to decodable or do i need to parse it manually?
Please advice
For the first question, you have a key called Room so it has to decode that key,
is it possible to not have it sure, instead of parsing that JSON data first call out the value of that key JSON["Rooms"], and parse what inside as a [Room].self ,
For the second question if the count is unlimited, as if you don't know how much Room key count are going to be, the Decoder abilities are limited then, however you can always map out the values as Dictionary and then decode the values as Room without caring about the key, this trick will do but you will abandon the original Key.
Update for the second case:
Check out this code below.
typealias jsonDictionary = [String: Any]
let jsonData = json.data(using: .utf8)! // converting test json string to data
var arrayOfRooms: [Room] = []
do {
let serialized = try JSONSerialization.jsonObject(with: jsonData, options: []) // serializing jsonData to json object
if let objects = serialized as? [String: Any] { //casting to dictionary
for key in objects.keys { //looping into the keys rooms (n) number
let rooms = objects[key] // getting those rooms by key
let data = try JSONSerialization.data(withJSONObject: rooms!, options: []) //converting those objects to data again to parse
var myRoom = try! JSONDecoder().decode([Room].self, from: data) // decoding each array of rooms
arrayOfRooms.append(contentsOf: myRoom) // appending rooms to main rooms array declared on top
print("Data", data) // just to check
}
print("MY Array Of Rooms Count \(arrayOfRooms.count)")
} else {
print("nil")
}
} catch {
}
Answer #1: Yes, it's possible with nestedContainers but the effort is greater than the benefit.
Answer #2: Decode the dictionary as [String:Room] or use custom coding keys described in this answer

Unwrapping JSON from Itunes API - IOS App

Having an issue with my program. I would appreciate it if someone could help out. I have tried for weeks to parse the JSON files fetched from the iTunes API
(itunes.apple.com/search?term=song+you+want+to+search&entity=songTrack).
However, my answers are never displayed on my tableview and an error always shows up in the terminal:
"2017-11-14 17:25:28.809190+0100 Itunes Learning[32409:6240818] [MC] Lazy loading NSBundle MobileCoreServices.framework
2017-11-14 17:25:28.810264+0100 Itunes Learning[32409:6240818] [MC] Loaded MobileCoreServices.framework
2017-11-14 17:25:28.823734+0100 Itunes Learning[32409:6240818] [MC] System group container for systemgroup.com.apple.configurationprofiles path is /Users/cyprianzander/Library/Developer/CoreSimulator/Devices/D52FD9D5-B6E4-4CE0-99E4-6E0EE15A680D/data/Containers/Shared/SystemGroup/systemgroup.com.apple.configurationprofiles
Could not cast value of type '__NSDictionaryI' (0x103b911d8) to 'NSArray' (0x103b90d28).
2017-11-14 17:25:29.875534+0100 Itunes Learning[32409:6240900] Could not cast value of type '__NSDictionaryI' (0x103b911d8) to 'NSArray' (0x103b90d28).
(lldb) "
This is approximately how the JSON file is set up:
{“resultCount” : 50, “results”: [ {“trackName”:”name”, ”artistName”:”name2”}, {“trackName”:”name3”, “artistName”:”name4”} ] }
(An array of objects inside an array - meaning the first object is on the far outside).
I have tried my function with another API, which did work. I have the feeling that the main reason as to why this happens, is because the iTunes API JSON file is very complex. It is an assortment of very long objects inside an array, which is inside a smaller list of objects. However, the other one was only and array of objects.
Here is my code: (I have noticed that the problem occurs while parsing the data I need. The only thing I need to know is how to properly unwrap my JSON file)
func parseData(searchTerm: String) {
fetchedSong = []
let itunesSearchTerm = searchTerm.replacingOccurrences(of: " ", with: "+", options: .caseInsensitive, range: nil)
let escapedSearchTerm = itunesSearchTerm.addingPercentEncoding(withAllowedCharacters: [])!
let urlString = "https://itunes.apple.com/search?term=\(escapedSearchTerm)&entity=song"
let url = URL(string: urlString)!
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let error = error {
// If there is an error in the web request, print it to the console
print(error)
return
}
else {
do {
let fetchedData = try JSONSerialization.jsonObject(with: data!, options: .mutableLeaves) as! NSArray
print(fetchedData)
for eachFetchedSong in fetchedData {
let eachSong = eachFetchedSong as! [String: Any]
let song = eachSong["trackName"] as! String
let artist = eachSong["artistName"] as! String
self.fetchedSong.append(songs(song: song, artist : artist))
}
self.SongTableView.reloadData()
}
catch {
print("An error occured while decoding the JSON object")
}
}
}.resume()
}
If anyone could help me, I would be extremely happy, especially because I have been stuck with this for three weeks, continuously trying different techniques (this one seemed the most successful).
Your JSON data is not an array. It is a dictionary with two key/value pairs. The first is the key "resultCount" with a value of 50, and the second is the key "results" with an array as its value.
Never use as! when parsing JSON, since this will crash your app if you get an unexpected result. Don't use .mutableLeaves unless you can explain to us what it does and why you need it. Don't use NSArray in your Swift code.
Handling one error and crashing on others is pointless. I'd write
if let fetchedDict = try? JSONSerialization(...) as? [String:Any],
let fetchedArray = fetchedDict ["results"] as? [[String:Any]] {
for dict in fetchedArray {
if let song = dict ["trackName"] as? String,
let artist = dict ["artistName"] as? String {
...
}
}
}

JSON Serialization not working properly

I've been learning the basics of JSON and I am trying to read data from a JSON file that I have written. The JSON file looks like this:
gradeBoundaries = {
"Subjects": [
{
"Title":"Biology HL",
"Boundaries":
{
1:[0,15],
2:[16,27],
3:[28,39],
4:[40,52],
5:[53,64],
6:[65,77],
7:[78,100]
}
}
]
}
The code that I am using to take the data from this file is as follows:
if let url = Bundle.main.url(forResource: "gradeBoundaries", withExtension: "json") {
do {
let jsonData = try Data(contentsOf: url)
do {
let jsonResult: [([String: String], [String: [Int : [Int]]] )] = try JSONSerialization.jsonObject(with: jsonData, options: JSONSerialization.ReadingOptions.mutableContainers) as! [([String: String], [String: [Int : [Int]]] )] //the entire object
} catch {}
} catch {}
}
When I run the code above, everything works fine until this line:
let jsonResult: [([String: String], [String: [Int : [Int]]] )] = try JSONSerialization.jsonObject(with: jsonData, options: JSONSerialization.ReadingOptions.mutableContainers) as! [([String: String], [String: [Int : [Int]]] )] //the entire object
As you can see I am trying to cast jsonResult as a fairly complicated data structure. I have tried many others including NSDictionary and Array but none of them seem to make a difference. Since I am so new to JSON I could just be misinterpreting the format of the data, and if anyone could point me in the right direction that would be great.
However, it is indeed nothing to do with the casting, then I am even more lost. This is the way that many SO answers have said to read the data, but it simply does not work for me.
I even tried switching between Data and NSData to no effect. I want to be able to break this data down into smaller pieces, but my program keeps on getting stuck on this line, so I need some help. Thanks!
EDIT
Editing the data type to Any did not allow the line to execute:
let jsonResult: Any = try JSONSerialization.jsonObject(with: jsonData, options: JSONSerialization.ReadingOptions.mutableContainers) as! Any
EDIT: 31 Dec 2016
Trying to make it work as below was ineffective:
typealias JSONDictionary = [String: Any]
if let url = Bundle.main.url(forResource: "gradeBoundaries", withExtension: "json") {
do {
let jsonData = try Data(contentsOf: url)
if let jsonResult: JSONDictionary = try JSONSerialization.jsonObject(with: jsonData, options: []) as? JSONDictionary {
print("success!")
}
} catch {}
}
However, it seems like a really good idea, so I think there must be something seriously wrong about my JSON or I'm doing something really stupid in my code.
EDIT
The JSON that I have been using is apparently invalid. I modified it now to be this:
{
"Subjects": [
{
"Title":"Biology HL",
"Boundaries":
{
1:[0,15],
2:[16,27],
3:[28,39],
4:[40,52],
5:[53,64],
6:[65,77],
7:[78,100]
}
}
]
}
First of all a message to all writers of online tutorials:
Please stop suggesting the reading option .mutableContainers in Swift. It's completely meaningless because mutability is provided for free by assigning the object to a variable.
Don't confuse yourself by annotating that weird snake-type.
To understand the structure read the hierarchy. [] is an array, {} a dictionary.
For convenience declare a type alias for a JSON dictionary
typealias JSONDictionary = [String:Any]
Then walk through the JSON (assuming the root object is a dictionary):
do {
if let jsonResult = try JSONSerialization.jsonObject(with:jsonData, options: []) as? JSONDictionary {
if let subjects = jsonResult["Subjects"] as? [JSONDictionary] {
for subject in subjects {
print(subject["Title"] as? String)
if let boundaries = subject["Boundaries"] as? JSONDictionary {
for (key, value) in boundaries {
print(key, value)
}
}
}
}
}
} catch {
print(error)
}
Regarding the JSON the keys in the Boundary dictionary must be strings:
{
"Subjects": [
{
"Title":"Biology HL",
"Boundaries":
{
"1":[0,15],
"2":[16,27],
"3":[28,39],
"4":[40,52],
"5":[53,64],
"6":[65,77],
"7":[78,100]
}
}
]
}
So I figured it out and feel like an idiot. After using Java and Swift for the longest time, I thought I would leave some comments on my JSON.
The JSON I was actually using was this:
{
"Subjects": [//[([String: String], [String: [Int : [Int]]] )]
{
"Title":"Biology HL", //[String: String]
"Boundaries": //[String: [Int: [Int]]]
{
"1":[0,15], //[Int: [Int]]
"2":[16,27],
"3":[28,39],
"4":[40,52],
"5":[53,64],
"6":[65,77],
"7":[78,100]
}
}
]
}
However, I thought that I would leave the comments out of the SO question because they weren't necessary.
After copying #vadian's suggested JSON my code worked. I then came up with this hypothesis and tried adding a comment to his JSON, and needless to say it didn't work.
So I guess the moral of this story is read up about commenting on code before you actually do it.

Optional String added to output - Swift

Been working on pulling data from an API, finally got that to work (I believe correctly) but when I print out variables within the dictionary, "Optional" gets added to the string
do {
let jsonResult = try NSJSONSerialization.JSONObjectWithData(data!, options:NSJSONReadingOptions.MutableContainers) as? NSDictionary
print(jsonResult)
if (jsonResult != nil) {
if let players = jsonResult?["players"] as? [Dictionary<String, AnyObject>] {
for player in players {
let personID = player["name"]
print(personID)
}
}
} else {
print("No Data")
// couldn't load JSON, look at error
}
}
Sample of the JSON Output:(note I cut this down and used only the first element)
Optional({
players = (
{
name = "Derek Anderson";
},
Print statement:
Optional(Derek Anderson)
My assumption here is that its printing the Optional Dictionary and then grabbing the "players" name. Meaning I haven't pulled just that name out. I've pulled the name out of the players array, not the Optional.
I feel like I may be adding that Optional Array in some way though, cant seem to figure this one out!
Solved, the variable needs to be unwrapped:
do {
let jsonResult = try NSJSONSerialization.JSONObjectWithData(data!, options:NSJSONReadingOptions.MutableContainers) as? NSDictionary
print(jsonResult)
if (jsonResult != nil) {
if let players = jsonResult?["players"] as? [Dictionary<String, AnyObject>] {
for player in players {
let personID = player["name"]!
print(personID)
}
}
} else {
print("No Data")
// couldn't load JSON, look at error
}
}

Cannot properly parse JSON Data because of its format?

I am trying to parse data which look:
It looks like each record is sequential.. 0, 1, 2 and then within each record there are lots of key value pairs such as the name or showID.
I want to go into each record and only get certain pairs, for example the name, showID and Date.
Here is my code, I am unsure what should be my modal in for item in loop
in other words, how do I get the specific fields into my empty dictionary array?
let task = NSURLSession.sharedSession().dataTaskWithURL(url) { (data, response, error) -> Void in
if let urlContent = data
{
do
{
var jsonResult:NSDictionary = try NSJSONSerialization.JSONObjectWithData(urlContent, options: NSJSONReadingOptions.MutableContainers) as! NSDictionary
if let items = jsonResult["items"] as! NSArray?
{
var emptyArrayOfDictionary = [[String : AnyObject]]()
for item in 0...jsonResult.count
{
}
}
The idea would be to create a struct (or a class) which contains the properties you need, created with an initializer from the values in your dictionaries.
Let's say you want to make "Show" objects containing the show name and the show ID.
You could create a struct like this:
struct Show {
let name:String
let showID:Int
init?(dictionary: [String:AnyObject]) {
guard let name = dictionary["name"] as? String,
let showID = dictionary["showID"] as? Int else {
return nil
}
self.name = name
self.showID = showID
}
}
Then iterate over your dictionaries and pass each one to the struct initializer, something like this:
let task = NSURLSession.sharedSession().dataTaskWithURL(url) { (data, response, error) -> Void in
if let urlContent = data {
do {
if let jsonResult = try NSJSONSerialization.JSONObjectWithData(urlContent, options: []) as? [String : AnyObject] {
if let items = jsonResult["items"] as? [[String : AnyObject]] {
let shows = items.flatMap { Show(dictionary: $0) }
}
}
} catch {
print(error)
}
}
}
The struct initializer is an Optional one, meaning that if the dictionary does not contain the keys "name" or "showID" it will not create the object and will return nil instead; that's why we're using flatMap to iterate instead of map (because flatMap unwraps the Optionals).
Now you have an array of objects, shows, and you can filter or sort its contents easily with Swift methods like sort, filter, etc.
Each object in the shows array is a Show object and has name and showID properties with the data of your dictionaries.
What flatMap does is create an array of Show objects by iterating (like a loop) over the initial array. On this line:
let shows = items.flatMap { Show(dictionary: $0) }
the $0 represents the current array element. What it means is that for each element in the items array, we take it and create a new Show instance with it, and put the resulting array of objects in the constant shows.
There's also map which is often used, but here the init of our Show struct is an optional init, so it returns an Optional Show, and flatMap knows how to deal with this (it will safely unwrap the optional and ignore the nil ones) where map does not.
If you would like to simplify your son parsing try this Open source https://github.com/SwiftyJSON/SwiftyJSON
With this you access name field of item 0
let userName = json[0]["name"].string