Fetching every object in nested JSON - ios

So here you can see my method which fetches JSON. My problem is that I want my loop to go through every object. Not one object 10 times like it is now. I know that player["1"] is causing it to fetch first object repeatedly, it is just for the sake of the example. I need to get every player information. Could someone please fix this logic and little bit explain the situation.
var homeTeamPlayers: [MatchUp]? = []
let urlRequest = URLRequest(url: URL(string: "http://www.fibalivestats.com/data/586746/data.json")!)
func fetchHomeTeam() {
let task = URLSession.shared.dataTask(with: urlRequest) { (data,response,error) in
if error != nil {
print(error!)
return
}
homeTeamPlayers = [MatchUp]()
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as! [String : AnyObject]
if let teamsFromJSON = json["tm"] as? [String : AnyObject] {
if let homeTeam = teamsFromJSON["1"] as? [String : AnyObject] {
if let player = homeTeam["pl"] as? [String : AnyObject] {
for _ in player {
let homeTeamPlayer = MatchUp()
if let firstPlayer = player["1"] as? [String : AnyObject] {
if let name = firstPlayer["name"] as? String {
homeTeamPlayer.homeTeam_name = name
}
}
homeTeamPlayers?.append(homeTeamPlayer)
}
}
}
}
} catch let error {
print(error)
}
}
task.resume()
}
Here is the JSON I would like to fetch...
{
tm: {
1: {
pl: {
1: {
name: "R. Miniotas"
},
2: {
name: "T. Delininkaitis"
},
3: {
name: "V. Cizauskas"
},
4: {
name: "T. Klimavicius"
},
5: {
name: "V. Lipkevicius"
},
6: {
name: "M. LinkeviÄius"
},
7: {
name: "D. Seskus"
},
8: {
name: "T. Michnevicius"
},
9: {
name: "D. Gvezdauskas"
},
11: {
name: "A. Valeika"
}
}

You need to enumerate the dictionary using for (key, value) in syntax:
if let players = homeTeam["pl"] as? [String : Any] {
for (_, value) in players {
let homeTeamPlayer = MatchUp()
if let currentPlayer = value as? [String : Any],
let name = currentPlayer["name"] as? String {
homeTeamPlayer.homeTeam_name = name
}
homeTeamPlayers?.append(homeTeamPlayer)
}
}
However to avoid empty Matchup instances I'd recommend
if let players = homeTeam["pl"] as? [String : Any] {
for (_, value) in players {
if let currentPlayer = value as? [String : Any],
let name = currentPlayer["name"] as? String {
let homeTeamPlayer = MatchUp()
homeTeamPlayer.homeTeam_name = name
homeTeamPlayers?.append(homeTeamPlayer)
}
}
}
Note: The JSON dictionary in Swift 3 is [String:Any] and why is the homeTeamPlayers array optional?
And finally – as always - .mutableContainers is completely meaningless in Swift.

Your JSON data is a dictionary of dictionaries of dictionaries.
It looks like there is an outer key "tm" (teams) that then contains a dictionary with keys "1", "2", etc for each team, and then inside the teams, there are more dictionaries that again use the keys "1", "2", "3", etc for the players. This is not a great structure.
If you don't care about the order in which you fetch the items from your dictionaries then you can use the syntax:
for (key, value) in dictionary
...to loop through all the key/value pairs in a dictionary, but you do need to be aware that the order you get the key/value pairs is not defined. It might give you the entries in key order sometimes, and not in order other times.
If order is important then you would need to fetch the keys first, sort them, and then fetch the items (or some other technique). Getting the values sorted by key might look like this:
let keys = dictionary.keys.sorted($0 < $1)
for aKey in keys {
let aValue = dictionary[aKey]
//Do whatever you need to do with this entry
}
EDIT:
Your JSON data needed some editing to make it legal JSON:
{
"tm" : {
"1" : {
"pl" : {
"7" : {
"name" : "D. Seskus"
},
"3" : {
"name" : "V. Cizauskas"
},
"8" : {
"name" : "T. Michnevicius"
},
"4" : {
"name" : "T. Klimavicius"
},
"11" : {
"name" : "A. Valeika"
},
"9" : {
"name" : "D. Gvezdauskas"
},
"5" : {
"name" : "V. Lipkevicius"
},
"1" : {
"name" : "R. Miniotas"
},
"6" : {
"name" : "M. LinkeviÃÂius"
},
"2" : {
"name" : "T. Delininkaitis"
}
}
}
}
}
(All the keys and values had to be enclosed in quotes, and I needed to add closing braces to terminate the object graph)
I wrote code that took the above and read it into a JSON object, and then wrote it back out to JSON without whitespace. I then converted all the quotes to \".
Here is tested code that parses the JSON into objects, and then walks your data structure, with quite a bit of error checking:
let jsonString = "{\"tm\":{\"1\":{\"pl\":{\"7\":{\"name\":\"D. Seskus\"},\"3\":{\"name\":\"V. Cizauskas\"},\"8\":{\"name\":\"T. Michnevicius\"},\"4\":{\"name\":\"T. Klimavicius\"},\"11\":{\"name\":\"A. Valeika\"},\"9\":{\"name\":\"D. Gvezdauskas\"},\"5\":{\"name\":\"V. Lipkevicius\"},\"1\":{\"name\":\"R. Miniotas\"},\"6\":{\"name\":\"M. LinkeviÃius\"},\"2\":{\"name\":\"T. Delininkaitis\"}}}}}"
guard let data = jsonString.data(using: .utf8) else {
fatalError("Unable to convert string to Data")
}
var jsonObjectOptional: Any? = nil
do {
jsonObjectOptional = try JSONSerialization.jsonObject(with: data, options: [])
} catch {
print("reading JSON failed with error \(error)")
}
guard let jsonObject = jsonObjectOptional as? [String: Any] else {
fatalError("Unable to read JSON data")
}
//Fetch the value for the "tm" key in the outer dictionary
guard let teamsFromJSON = jsonObject["tm"] as? [String : Any] else {
fatalError("Can't fetch dictionary from JSON[\"tm\"]")
}
let teamKeys = teamsFromJSON
.keys //Fetch all the keys from the teamsFromJSON dictionary
.sorted{$0.compare($1, options: .numeric) == .orderedAscending} //Sort the key strings in numeric order
print("teamsFromJSON = \(teamsFromJSON)")
//loop through the (sorted) team keys in teamKeys
for aTeamKey in teamKeys {
guard let aTeam = teamsFromJSON[aTeamKey] as? [String: Any] else {
print("Unable to read array of teams")
continue
}
//Fetch the dictionary of players for this team
guard let playersDict = aTeam["pl"] as? [String: Any] else {
print("Unable to read players dictionary from team \(aTeamKey)")
continue
}
//Fetch a sorted list of player keys
let playerKeys = playersDict
.keys
.sorted{$0.compare($1, options: .numeric) == .orderedAscending}
print("playerKeys = \(playerKeys)")
//Loop through the sorted array of player keys
for aPlayerKey in playerKeys {
//Fetch the value for this player key
guard let aPlayer = playersDict[aPlayerKey] as? [String: String] else {
print("Unable to cast aPlayer to type [String: String]")
continue
}
//Attempt to read the "name" entry for this player.
guard let playerName = aPlayer["name"] else {
continue
}
//Deal with a player in this team
print("In team \(aTeamKey), player \(aPlayerKey) name = \(playerName)")
}
}
if let jsonData = try? JSONSerialization.data(withJSONObject: jsonObject, options: []),
let jsonString = String(data: jsonData, encoding: .ascii) {
print("\n\njsonString = \n\(jsonString)")
}
The output of that code is:
playerKeys = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "11"]
In team 1, player 1 name = R. Miniotas
In team 1, player 2 name = T. Delininkaitis
In team 1, player 3 name = V. Cizauskas
In team 1, player 4 name = T. Klimavicius
In team 1, player 5 name = V. Lipkevicius
In team 1, player 6 name = M. LinkeviÃius
In team 1, player 7 name = D. Seskus
In team 1, player 8 name = T. Michnevicius
In team 1, player 9 name = D. Gvezdauskas
In team 1, player 11 name = A. Valeika
(Your data is missing an entry for player 10.)

Suppose this is my URL:
URL: https://maps.googleapis.com/maps/api/directions/json?origin=19.0176147,72.8561644&destination=26.98228,75.77469&sensor=false
The URL mentioned above when entered in browser gives me a JSON response from google.
In this response, I have to fetch the value of distance and duration.
So my swift code to fetch the value is:
do{
let data = try Data(contentsOf: url!)
let json = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) as? [String: AnyObject]
if let routes = json?["routes"] as AnyObject? as? [[String: AnyObject]] {
//print(routes)
for r in routes {
if let legs = r["legs"] as? [[String: AnyObject]] {
for l in legs {
//Start Address
print("Start Address: \((l["start_address"]!) as Any)")
// End Address
print("End Address: \((l["end_address"]!) as Any)")
//Distance
if let distance = l["distance"] as? [String: AnyObject] {
distanceResult = (distance["text"]!) as Any as? String
print("Distance: \(distanceResult!)")
}
// Duration
if let duration = l["duration"] as? [String: AnyObject] {
durationResult = (duration["text"]!) as Any as? String
print("Duration: \(durationResult!)")
}
}
googleResult = distanceResult+durationResult
}
}
}
}
catch
{
distanceResult = ""
durationResult = ""
print("error in JSONSerialization")
}
if(googleResult != ""){
googleResult = "Distance: \(distanceResult!), Duration: \(durationResult!)"
}
else{
print("googleResult is nil")
distanceResult = ""
durationResult = ""
}
return googleResult

Related

How to Get JSON array which will be later stored in Realm table?

This is part of JSON my API returns, I don't know how to get that and store in Realm table?
"images": {
"image1": {
"url": "http://www.example.com/900150983cd24fb0d6963f7d28e17f72.gif",
"path": "data/2clb91218/900150983aassfb0d6963f7d28e17f72.gif",
"alt": "YWJj"
},
"image2": {
"url": "http://example/tex/fbce4a1ebe576539394e9493e30c7e5e.gif",
"path": "data/2clb91218/900150983cd24fb0d6963f7d28e332g22.gif",
"alt": "Yaaase22"
},
"image3": {
"url": "https://example.amazonaws.com/card/480_300/9/9jpul1218.jpg",
"path": "data/2clb91218/ewfregwrgw6963f7d28e17f72.gif",
"alt": "pic3"
}
}
NOTE : Dont test these urls, I didnt want to put real ones.
NOTE : In object 'Images', child-object's names are chaning so we have: 'image1' , 'image2' etc.
I have read on Stackoverflow that for storing arrays in RealmTable, we need to use non-dynamic List<> , so this is how I store image-urls in class:
var images = List<String>()
This is part of my responseJSON function where I am trying to Get images from API, 'continue' is there just to Get another objects if images are unable to Get :
guard let images = data["images"] as? [String: Any?],
let image = images["image1"] as? [String: String],
let imageUrl = image["url"] as? String
else { print("error") ; continue }
This above works for 'image1', but what about other image names, how to handle them?
And one more problem - 'Images' object can return NIL if there are no images which can aslo happen. If it's nil, I want to put "null" in RealmTable's column 'images' of the type I mentioned above.
I hope you can fully understand me, if not, comment and I will try to explain better.
You need
guard let images = data["images"] as? [String:[String:Any]] else { return }
let res = Array(images.values)
let urls = res.compactMap { $0["url"] as? String }
print("All urls : ",urls)
var images = List<String>()
let imagesDictInfo = responseData.toDictionary()
if let imageArray = imagesDictInfo["Images"] as? [[String:Any]]{
let imageArrayData = image.getJson()
//Store this ImageArray into realm or
for image in imageArray{
let imageData = image.getJson()
//Store Image Data
images.append(imageData)
}
}
func getJson(_ jsonString:String) -> [String:Any]?{
do{
let data = jsonString.data(using: .utf8)
if let json = try? JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [String:Any] {
return json
}else{
return nil
}
}catch{
return nil
}
}
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
}

Swift Compact Map returning empty

Hi I am trying to learn RXSwift and First time I came across these concepts like Maps and Compact Maps.
I am able to get the response, but this line always returns empty.
objects.compactMap(DummyUser.init)
fileprivate let Users = Variable<[DummyUser]>([])
fileprivate let bag = DisposeBag()
response
.filter { response, _ in
return 200..<300 ~= response.statusCode
}
.map { _, data -> [[String: Any]] in
guard (try? JSONSerialization.jsonObject(with: data, options: [])) != nil else {
return []
}
let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String : Any]
// print(json!["results"])
return json!["results"] as! [[String : Any]]
}
.filter { objects in
return objects.count > 0
}
.map { objects in
// objects.forEach{print($0["name"]!)}
let names = objects.map { $0["name"]!}
print(names)
return objects.compactMap(DummyUser.init)
}
.subscribe(onNext: { [weak self] newEvents in
self?.processEvents(newEvents)
})
.disposed(by: bag)
func processEvents(_ newEvents: [DummyUser]) {
var updatedEvents = newEvents + Users.value
if updatedEvents.count > 50 {
updatedEvents = Array<DummyUser>(updatedEvents.prefix(upTo: 50))
}
Users.value = updatedEvents
DispatchQueue.main.async {
self.MianUsertable.reloadData()
}
// refreshControl?.endRefreshing()
let eventsArray = updatedEvents.map{ $0.dictionary } as NSArray
eventsArray.write(to: userFileURL, atomically: true)
}
My Json Response is Here
https://randomuser.me/api/?results=5
DummyUser Class
import Foundation
typealias AnyDict = [String: Any]
class DummyUser {
let gender: String
let name: AnyDict
let dob: String
let picture: AnyDict
init?(dictionary: AnyDict) {
guard let Dgender = dictionary["gender"] as? String,
let Dname = dictionary["name"] as? AnyDict,
let birthdata = dictionary["dob"] as? AnyDict,
let Ddob = birthdata["dob"] as? String,
let Dpicture = dictionary["picture"] as? AnyDict
else {
return nil
}
gender = Dgender
name = Dname
dob = Ddob
picture = Dpicture
}
var dictionary: AnyDict {
return [
"user": ["name" : name, "gender": gender, "dob": dob],
"picture" : ["userImage": picture]
]
}
}
In your DummyUser model you are using failable initializer, so in case of wrong dictionary provided to init method it will return nil.
compactMap automatically automatically filters nil's and that's the reason why your output is empty.
Looking at this piece of code:
let names = objects.map { $0["name"]!}
return objects.compactMap(DummyUser.init)
I would debug this variable called names because it probably has wrong input for the DummyUser initializer. It should be dictionary containing all of your DummyUser parameters. You can also debug your failable initializer to see which of the parameter is missing.

create a dictionary from json data values

I'm trying to create a dictionary from 2 json values, so far I can get one and append to an array but instead I would like to combine 2 values as a dictionary and add them to an array. This is what my function currently looks like:
var menuButtonsArray: [String?] = []
func getAppMenuButtons() {
guard let appJSON = appMenuJSON else {return}
guard let items = appJSON["items"] as? [[String: Any]] else {
return
}
let filteredItem = items.filter{$0["itemId"] as! String == "Items"}[0]
if let data = filteredItem["data"] as? [[String:Any]] {
for embeddedDict in data {
for (key, value) in embeddedDict {
if key == "name" {
menuButtonsArray.append(value as! String)
print("key: \(value)")
}
}
}
}
}
This is what the json looks like, it's shortened to fit this post:
"itemId": "Items",
"data": [
{
"name": "Item1",
"url": "www.item1.com"
},
{
"name": "Item2",
"url": "www.item2.com"
},
{
"name": "Item2",
"url": "www.item2.com"
}
]
What I'm trying to do is create an array of dictionaries with the format:
name:url
so something like :
["item1":"www.item1.com","item2":"www.item2.com", "item3":"www.item3.com"]
Here is an example on how you can use map() get the array you want:
var data = [["name": "Item1","url": "www.item1.com"],
["name": "Item2","url": "www.item2.com"],
["name": "Item3","url": "www.item3.com"]]
var newData = data.map { (dict: Dictionary) -> Dictionary<String, String> in
return [dict["name"]!:dict["url"]!] }
This code should give you
[ ["Item1": "www.item1.com"], ["Item2": "www.item2.com"], ["Item3":
"www.item3.com"] ]
as output
First of all if you need a Dictionary then declare your result as Dictionary instead of Array, you can use subscribe to use your embeddedDict["name"] as key in your resultDictionary and your embeddedDict["url"] as your resultDictionary value
func getAppMenuButtons() {
guard let appJSON = appMenuJSON else {return}
guard let items = appJSON["items"] as? [[String: Any]] else {
return
}
let filteredItem = items.filter{$0["itemId"] as! String == "Items"}[0]
var resultDict : [String:String] = [:]
if let data = filteredItem["data"] as? [[String:Any]] {
guard embeddedDict["name"] != nil && embeddedDict["url"] else{
continue
}
for embeddedDict in data {
resultDict[embeddedDict["name"]] = embeddedDict["url"]
}
}
debugPrint(resultDict) //in resultDict you will have your desired result
}

Convert JSON nested objects

I am getting the following JSON from Foursquare API and I have been struggling with extracting the data:
{
"meta":{
"code":200,
"requestId":"58122e59498e5506a1b23580"
},
"response":{
"venues":[
{
"id":"4d56c381a747b60cd4a12c2b",
"name":"Sports Circle",
"contact":{},
"location":{
"lat":31.9,
"lng":35.9,
"labeledLatLngs":[
{
"label":"display",
"lat":31.9,
"lng":35.90
}
],
],
"confident":true
}
}
}
I want to get the name in venues in addition to the lat and lng values. I have tried this so far but it gets out of the second if statement at JVenues because it is nil:
func parseData (JSONData: Data){
do {
var readableJson = try JSONSerialization.jsonObject(with: JSONData, options: .mutableContainers) as! [String:AnyObject]
if let JResponse = readableJson ["response"] as? [String:AnyObject] {
if let JVenues = JResponse["venues"] as? [String:AnyObject]{
if let JName = JVenues["name"] as? String{
NSLog(JName)
}
}
}
} catch {
print(error)
}
}
This is what the other answers are getting at. Will probably make more sense if you can see it all laid out...
if let JResponse = readableJson ["response"] as? [String : AnyObject] {
if let JVenues = JResponse["venues"] as? [[String : AnyObject]] {
if let JName = JVenues.first?["name"] as? String {
NSLog(JName)
}
}
}
Note this only gets the FIRST name in the array of venues.
EDIT:
I prefer something like this. Define a struct and convert your dictionaries to the struct:
struct Venue {
var name: String?
var venueId: String?
init(_ venueDictionary: [String : AnyObject]) {
self.name = venueDictionary["name"] as? String
self.venueId = venueDictionary["id"] as? String
}
}
In your class create a property such as:
var venues = [Venue]()
From your JSON map the dictionaries to the venue array. I renamed variables that start with a capital for convention.
if let response = readableJson ["response"] as? [String : AnyObject] {
if let responseVenues = response["venues"] as? [[String : AnyObject]] {
self.venues = responseVenues.map({ Venue($0)) })
}
}
Use anywhere in your class like:
let venue = self.venues.first
print(venue?.name)
Or:
if let venue = self.venues.find({ $0.name == "Sports Circle" }) {
print("found venue with id \(venue.venueId)")
}

Get info JSON Google Books - IOS

how can I get authors from a google's book with JSON in Swift? This code works but I have a problem with authors.
This is an example of JSON:
{
"kind": "books#volume",
"id": "17BYrgEACAAJ",
"etag": "snlcLszwWRo",
"selfLink": "https://www.googleapis.com/books/v1/volumes/17BYrgEACAAJ",
"volumeInfo": {
"title": "Scomparso. Dave Brandstetter mysteries",
"authors": [
"Joseph Hansen"
],
"publisher": "Elliot",
"publishedDate": "2012-01",
func getBookInfo:
func getBookInfo(isbn: String) {
let url_book = "https://www.googleapis.com/books/v1/volumes?q=isbn:" + isbn
if let url = NSURL(string: url_book) {
NSURLSession.sharedSession().dataTaskWithURL(url, completionHandler: {data, _, error -> Void in
if let error = error {
print(error.localizedDescription)
} else {
let data = data,
jsonResult = try? NSJSONSerialization.JSONObjectWithData(data!, options: []),
arrayOfTitles = jsonResult!.valueForKeyPath("items.volumeInfo.title") as? [String],
arrayOfAuthors = jsonResult!.valueForKeyPath("items.volumeInfo.authors") as? [String],
arrayOfPublishers = jsonResult!.valueForKeyPath("items.volumeInfo.publisher") as? [String],
arrayUrlImageBook = jsonResult!.valueForKeyPath("items.volumeInfo.imageLinks.smallThumbnail") as? [String]
self.titles = arrayOfTitles!.joinWithSeparator(", ")
self.authors = arrayOfAuthors!.joinWithSeparator(", ")
self.publishers = arrayOfPublishers!.joinWithSeparator(", ")
let url_image_book: String! = arrayUrlImageBook!.joinWithSeparator(", ")
self.title_label.text = self.titles
self.author_label.text = self.authors
if self.publishers != nil{
self.publisher_label.text = self.publishers
}
self.book_image.downloadedFrom(url_image_book!)
}
}).resume()
}
}
Thanks in advance.
So in order to retrieve the authors, I do something similar but a little bit different.
First, I just create an array of all the books that are returned by the API for the URL that I send to it...
if let volumeInfo = jsonResult.valueForKeyPath("items.volumeInfo") as? [AnyObject] {
if volumeInfo.count != 0 {
self.volumeInfo = volumeInfo
self.translateJSONData() //here is where I then translate the data
}
}
Then in my data translation function, I loop through all the results, and get the values I want.
for apiResponse in self.volumeInfo {
if let singleVolumeInfo = apiResponse as? [String : AnyObject] {
let bookCategory = singleVolumeInfo["categories"] as? [String]
let bookAuthors = singleVolumeInfo["authors"] as? [String]
//more information here
}
}
But that code to get the authors will give you an array of all the author's names.

Resources