How can I access values within Optional NSSingleObjectArrayI? - ios

I do not know how to access the 'duration' value within my nested Optional NSSingleObjectArrayI that is constructed from a JSON response. How do I access the nested values within this data structure?
When I call print(firstRow["elements"]), I get the following output:
Optional(<__NSSingleObjectArrayI 0x60000120f920>(
{
distance = {
text = "1.8 km";
value = 1754;
};
duration = {
text = "5 mins";
value = 271;
};
"duration_in_traffic" = {
text = "4 mins";
value = 254;
};
status = OK;
}
))
I have tried string indexing (firstRow['elements']['duration']) but am getting errors.
fetchData { (dict, error) in
if let rows = dict?["rows"] as? [[String:Any]]{
if let firstRow = rows[0] as? [String:Any]{
print("firstRow is")
print(firstRow["elements"])
// Trying to access duration within firstRow['elements'] here
}
}
}
For reference, this is the fetchData function:
func fetchData(completion: #escaping ([String:Any]?, Error?) -> Void) {
let url = getRequestURL(origin: "test", destination: "test")!;
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
do {
if let array = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String:Any]{
completion(array, nil)
}
} catch {
print(error)
completion(nil, error)
}
}
task.resume()
}
A sample HTTP JSON request is here:
https://maps.googleapis.com/maps/api/distancematrix/json?destinations=77%20Massachusetts%20Ave,%20Cambridge,%20MA&departure_time=now&key=AIzaSyB65D4XHv6PkqvWJ7C-cFvT1QHi9OkqGCE&origins=428%20Memorial%20Dr,%20Cambridge,%20MA

Seeing your output, your firstRow["elements"] is Optional, so you need to unwrap it. And it actually is an NSArray with a single element, where the only element is a Dictionary, with 4 entries -- "distance", "duration", "duration_in_traffic" and "status". You may need to cast the element to a Dictionary to access each entry.
You may use Optional binding with as?-casting for this purpose:
fetchData { (dict, error) in
if let rows = dict?["rows"] as? [[String: Any]] {
if let firstRow = rows.first {
print("firstRow is")
print(firstRow["elements"])
//Unwrap and cast `firstRow["elements"]`.
if let elements = firstRow["elements"] as? [[String: Any]] {
//The value for "duration" is a Dictionary, you need to cast it again.
if let duration = elements.first?["duration"] as? [String: Any] {
print(duration["text"] as? String)
print(duration["value"] as? Int)
}
}
}
}
}
Or too deeply nested ifs are hard to read, so someone would like it as:
fetchData { (dict, error) in
if
let rows = dict?["rows"] as? [[String: Any]],
let firstRow = rows.first,
let elements = firstRow["elements"] as? [[String: Any]],
let duration = elements.first?["duration"] as? [String: Any]
{
print(duration["text"] as? String)
print(duration["value"] as? Int)
}
}
Or using guard may be a better solution.
Or else, if you can show us the whole JSON text in a readable format, someone would show you how to use Codable, which is a modern way to work with JSON in Swift.

Related

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.

In the mentioned url i need to get only first dictionary from the url?

In this order detail array i am having 10 dictionaries but i need to display only first dictionary can any one help me how to implement this ?
http://www.json-generator.com/api/json/get/bUKEESvnvS?indent=2
here is my code shown below
func downloadJsonWithURL() {
let url = NSURL(string: self.url)
URLSession.shared.dataTask(with: (url as URL?)!, completionHandler: {(data, response, error) -> Void in
if let jsonObj = try? JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? NSDictionary {
self.orderdetailsArray = (jsonObj!.value(forKey: "Orders detail") as? [[String: AnyObject]])!
for array in self.orderdetailsArray {
let key = "OrderId"
let value = "#1000501"
for (key,value) in array{
if let addressDict = array as? NSDictionary{
if let orderid = addressDict.value(forKey: "OrderId"){
self.orderid.append(orderid as! String)
}
if let orderdate = addressDict.value(forKey: "OrderDate"){
self.orderdate.append(orderdate as! String)
}
if let subtotal = addressDict.value(forKey: "SubTotal"){
self.subTotal.append(subtotal as! Int)
}
if let Shipping = addressDict.value(forKey: "Shipping"){
self.shippingPrice.append(Shipping as! Int)
}
if let tax = addressDict.value(forKey: "Tax"){
self.tax.append(tax as! Int)
}
if let grandtotal = addressDict.value(forKey: "GrandTotal"){
self.grandTotal.append(grandtotal as! Int)
}
if let shippingAddress = addressDict.value(forKey: "ShippingAddress"){
self.shippingAddress.append(shippingAddress as AnyObject)
}
if let shippingMethod = addressDict.value(forKey: "ShippingMethod"){
self.shippingMethod.append(shippingMethod as AnyObject)
}
if let billingAddress = addressDict.value(forKey: "BillingAddress"){
self.billingAddress.append(billingAddress as AnyObject)
}
if let paymentMethod = addressDict.value(forKey: "PayMentMethod"){
self.paymentMethod.append(paymentMethod as AnyObject)
}
self.itemsArray = addressDict.value(forKey: "Items detail") as! [[String : AnyObject]]
}
}
}
OperationQueue.main.addOperation({
self.tableDetails.reloadData()
})
}
}).resume()
}
Do this. :
let jsonObj = try? JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? NSDictionary
guard let Ordersdetail = jsonObj["Orders detail"] as? [NSDictionary] else {
print("Cannot find key 'Orderdetails' in \(jsonObj)")
return
}
To access the contents of the first dictionary do this:
var orderid = Ordersdetail[0]["OrderId"]!
var shippingadress = Ordersdetail[0]["ShippingAddress"]!
var total = Ordersdetail[0]["GrandTotal"]!
var subtotal = Ordersdetail[0]["SubTotal"]!
var tax = Ordersdetail[0]["Tax"]!
var shipping = Ordersdetail[0]["Shipping"]!
Hi if you want first dictionary of that
self.orderdetailsArray
then
if let firstDictInfo = self.orderdetailsArray.first as? [String:Any] {
// Do your stuff here
print(firstDictInfo["OrderId"])
}
Instead of looping through the whole dictionary is dictionaries, you should just take the first dictionary and only parse that. There was also quite a few other conceptual problems with your code. In Swift, don't use NSDictionary, but use the native Swift version, Dictionary, which keeps the type information of its contents. Also, use conditional casting to make sure your program doesn't crash even if the received data is wrong/unexpected and don't use force unwrapping of optionals.
Also, when parsing a JSON response in Swift, in general it is not necessary and not a good idea to iterate through the key-value pairs of the dictionaries in the response. You should know what data structure you expect, otherwise you can't parse it properly and since you can directly access dictionary values in Swift if you know the key it corresponds to, there's no need to iterate through the dictionary in a loop.
func downloadJsonWithURL() {
let url = URL(string: self.url)
URLSession.shared.dataTask(with: url!, completionHandler: {(data, response, error) -> Void in
if let jsonObj = (try? JSONSerialization.jsonObject(with: data!, options: .allowFragments)) as? [String:Any] {
guard let self.orderdetailsArray = jsonObj["Orders detail"] as? [[String: AnyObject]] else {return}
guard let firstOrderDetails = self.orderdetailsArray.first else {return}
let key = "OrderId"
let value = "#1000501"
if let ordered = firstOrderDetails["OrderId] as? String {
self.orderid.append(orderid)
}
if let orderdate = firstOrderDetails["OrderDate"] as? String{
self.orderdate.append(orderdate)
}
if let subtotal = firstOrderDetails["SubTotal"] as? Int{
self.subTotal.append(subtotal)
}
if let shipping = firstOrderDetails["Shipping"] as? Int{
self.shippingPrice.append(shipping)
}
if let tax = firstOrderDetails["Tax"] as? Int{
self.tax.append(tax)
}
if let grandtotal = firstOrderDetails["GrandTotal"] as? Int{
self.grandTotal.append(grandtotal)
}
if let shippingAddress = firstOrderDetails[ "ShippingAddress"] as? AnyObject{ //why don't you store it as a String?
self.shippingAddress.append(shippingAddress)
}
if let shippingMethod = firstOrderDetails[ "ShippingMethod"] as? AnyObject{
self.shippingMethod.append(shippingMethod)
}
if let billingAddress = firstOrderDetails[ "BillingAddress"] as? AnyObject {
self.billingAddress.append(billingAddress)
}
if let paymentMethod = firstOrderDetails ["PayMentMethod"] as? AnyObject{
self.paymentMethod.append(paymentMethod)
}
guard let itemDetails = firstOrderDetails["Items detail"] as? [[String : AnyObject]] else {return}
self.itemsArray = itemDetails
}
}
}
OperationQueue.main.addOperation({
self.tableDetails.reloadData()
})
}
}).resume()
}
I haven't compiled and run the code, so make sure you check for any typos/inconsistencies. Also, make sure you change the types of the objects you store are AnyObjects to specific types.

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]] {

Retrieve specific Array from JSON in swift in the form of Array of Keys and Values?

JSON :
{
"11/08/22":[
{
"Bill Gates":"Microsoft",
"Steve Balmer":"Microsoft"
}],
"13/08/22":[
{
"Tim Cook":"Apple",
"Jony Ive":"Apple"
}]
}
Swift Code :
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)
//Get Result into Seperate Arrays
let keys = jsonResult.flatMap(){ $0.0 as? String }
let values = jsonResult.flatMap(){ $0.1 as? String }
}
} catch let error as NSError {
print(error)
}
})
jsonQuery.resume()
Requirements :
If i pass from program "11/08/22", I should be able to get all keys and values in the form of Array of String of only that array named "11/08/22" .
Better Explanation :
It should go into 11/08/22 and it should retrieve "Bill Gates","Steve Balmer" as Keys and "Microsoft" As a value in two separate arrays
For this example let's use an array to collect the people and a set to collect the companies:
var people: [String] = []
var companies: Set<String> = []
Subscript to the JSON dictionary with your "11/08/22" key and cast the result as an array of dictionaries.
Loop over this array and in the loop, add the keys to the people array and insert the values in the companies set.
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 {
if let dateContent = jsonResult["11/08/22"] as? [[String:String]] {
for group in dateContent {
people.appendContentsOf(group.keys)
group.values.forEach { companies.insert($0) }
}
}
}
} catch let error as NSError {
print(error)
}
})
jsonQuery.resume()
print(people)
print(companies)
Result:
["Steve Balmer", "Bill Gates"]
["Microsoft"]
let keys=jsonResult["11/08/22"]?.allKeys as? [String];
let values=jsonResult["11/08/22"]?.allValues as? [String];
It was as simple as this

Resources