Swift Accessing Values in Dictionary - ios

I have a swift dictionary and I am trying to access my values in my array.
My Dictionary that I make looks like this:
["results": {
Democrats = {
percent = 67;
raw = 4;
};
Republicans = {
percent = 33;
raw = 2;
};
"total_answers" = 6;
}, "success": 1]
I made another dictionary to get this:
let test = dictionary["results"] as! [String : AnyObject]
["Democrats": {
percent = 67;
raw = 4;
}, "Republicans": {
percent = 33;
raw = 2;
}, "total_answers": 6]
I can access values like:
let testing = test["total_answers"]
I want to access the values for percent and raw for example:
Democrats = {
percent = 67;
raw = 4;
};
The percent and raw key are static but the Democrats is a string that will never be the same.

I don't know what notation you're using for your dictionary but it doesn't compile in Swift.
A dictionary with [String:Any] as type would work but manipulating the data is going to be a type casting nightmare. You should consider using a regular structure where all values have the same type.
For example (using a typealias for the two value tuple):
typealias Votes = (percent:Int, raw:Int)
var results = [ "Democrats" : Votes(percent:67, raw:4),
"Repubicans" : Votes(percent:33, raw:2),
"Totals" : Votes(percent:100, raw:6)
]
let democratVotes = results["Democrats"]!.raw

So here let democrats:[String : Int] = test["Democrats"] as! [String : Int] will provide you new dictionary, containing only
Democrats = {
percent = 67;
raw = 4;
};

Related

sending parsed JSON data to views

I'm stuck because I can't send JSON data from URLSession func to views. I read the 90% of previous commends and watch lots of videos but I didn't migrate to my project. Here is my code blocks that I need help ;
This ones my json struct
struct APIResponse: Decodable{
let stocks: [Stocks]
}
struct Stocks: Decodable{
let id: Int
let difference: Float
let bid:Float
let isDown: Bool
let isUp: Bool
let offer: Float
let price: Float
let symbol: String
let volume: Double
}
this one is mine JsonDecode code block;
if let data2 = data2 {
do {
// let json = try JSONSerialization.jsonObject(with: data2, options: [])
let apiResponse = try JSONDecoder().decode(APIResponse.self, from: data2)
print(apiResponse.stocks[2].volume)
DispatchQueue.main.async {
completed()
}
}catch{
print(error)
}
}
}.resume()
when I watch videos about it they were [APIResponse].self but when I try that way my code is failed, in my way json parse is working (I can call like 'apiResponse.stocks[2].id') but I can't send this apiResponse data to views.
example of my JSON file
{
status = {
error = {
code = 0;
message = "";
};
isSuccess = 1;
};
stocks = (
{
bid = "31.5";
difference = "-0.2";
id = 1190;
isDown = 1;
isUp = 0;
offer = "31.6";
price = "31.81";
symbol = "P4jfFAYOTiLEih2Ic+NAkg==";
volume = "801457.5";
},
{
bid = "4.25";
difference = "-0.04";
id = 471;
isDown = 1;
isUp = 0;
offer = "4.26";
price = "4.31";
symbol = "zomIgqEl79jIE+TJ7xV4yQ==";
volume = "349264.21";
},
{
bid = "2.86";
difference = "-0.01";
id = 472;
isDown = 1;
isUp = 0;
offer = "2.87";
price = "2.87";
symbol = "2DlR317+autGo3fiKwNhFA==";
volume = "19279.4";
},
{
bid = 55;
difference = 1;
id = 473;
isDown = 0;
isUp = 1;
offer = "55.25";
price = "56.74";
symbol = "fvo0GQ+pqUmHXwm062Gatw==";
volume = "2647954.25";
}, {
bid = "1.22";
difference = "-0.04";
id = 465;
isDown = 1;
isUp = 0;
offer = "1.23";
price = "1.26";
symbol = "wR/24WChHVRFWZSUW1UdwQ==";
volume = "2206441.67";
}
);
}
First if you want to send your response back to the point from where initiated the API call you need to write completion handler and send your response model with the handler; you can take reference from Closures for Swift.
Also apart from that I noticed few errors in your decodable structure, for example you are expecting 'difference' as float type but the example JSON you have posted contains 'difference' as String and it applies for all your float and double values.
Also it will be a good practice If we will declare all the variable optional in decodable structure as if anytime any parameter won't come in response there won't be any problem in parsing it.

How to organize array of dictionaries based on the value of a key within them?

I have an array of information stored as follows:
(
{
actual = "<null>";
code = "AUTO.US";
date = "2019-12-31";
difference = "<null>";
estimate = "-0.12";
percent = "<null>";
"report_date" = "2020-03-27";
},
{
actual = "<null>";
code = "APTX.US";
date = "2019-12-31";
difference = "<null>";
estimate = "-0.5600000000000001";
percent = "<null>";
"report_date" = "2020-03-30";
},
{
actual = "<null>";
code = "BLAH.US";
date = "2019-12-31";
difference = "<null>";
estimate = "-0.5600000000000001";
percent = "<null>";
"report_date" = "2020-03-30";
}
);
I would like to create an array of dictionaries, a key for each report_date that contains an array of the above dictionaries that match that date. This is what I am trying to accomplish:
(
{
"2020-03-27" = (
{
actual = "<null>";
code = "AUTO.US";
date = "2019-12-31";
difference = "<null>";
estimate = "-0.12";
percent = "<null>";
"report_date" = "2020-03-27";
}
);
},
{
"2020-03-30" = (
{
actual = "<null>";
code = "APTX.US";
date = "2019-12-31";
difference = "<null>";
estimate = "-0.5600000000000001";
percent = "<null>";
"report_date" = "2020-03-30";
},
{
actual = "<null>";
code = "BLAH.US";
date = "2019-12-31";
difference = "<null>";
estimate = "-0.5600000000000001";
percent = "<null>";
"report_date" = "2020-03-30";
}
);
}
)
For clarification, I want to end up with an array that holds dictionaries with the date as the day, which hold as their object an array of the original dictionaries that have report_date matching that key. So there could be more than one earnings dictionary in the array under the key.
I appreciate any help with this!
You should never use NSDictionary or NSArray in Swift except in very rare circumstances. You should use Swift arrays and dictionaries. It would also help if you created an appropriate struct to hold your data rather than using raw dictionaries. If your data is coming from JSON then you can use Codable to convert your data quite simply.
Working with what you have, however, you need to convert an array of [String:String] dictionaries to an dictionary of arrays of [String:String], keyed by a[String]or the rather ugly[String:[[String:String]]]`:
To do so, simply iterate over the initial array, adding each dictionary to the [String:[[String:String]]] dictionary:
func createEarningsDictionary(from earnings:[[String:String]] ) -> [String:[[String:String]]] {
var arrayOfEarningsDicts = [String:[[String:String]]]()
for earning in earnings {
if let reportDate = earning["report_date"] {
arrayOfEarningsDicts[reportDate, default:[[String:String]]()].append(earning)
}
return arrayOfEarningsDicts
}
If you used an Earnings struct rather than the [String:String] dictionary, you would have the more readable:
func createEarningsDictionary(from earnings:[Earnings] ) -> [String:[Earnings]] {
var earningsDict = [String:[Earnings]]()
for earning in earnings {
earningsDict[earning.reportDate, default:[Earnings]()].append(earning)
}
return earningsDict
}
The question is a bit misleading, but:
the original data is an array of type [[String: String]] already, and the result that you require is an array of type [[String: [String: String]]].
follow the following code to get there:
// building the original data
var dic1: [String: String] = [:]
dic1["actual"] = "<null>"
dic1["code"] = "AUTO.US"
dic1["date"] = "2019-12-31"
dic1["difference"] = "<null>"
dic1["estimate"] = "-0.12"
dic1["percent"] = "<null>"
dic1["report_date"] = "2020-03-27"
var dic2: [String: String] = [:]
dic2["actual"] = "<null>"
dic2["code"] = "APTX.US"
dic2["date"] = "2019-12-31"
dic2["difference"] = "<null>"
dic2["estimate"] = "-0.5600000000000001"
dic2["percent"] = "<null>"
dic2["report_date"] = "2020-03-30"
// the array of the original data
let array = [dic1, dic2]
// the interesting function
func sortThemOut() {
var finalArray: [[String: [String: String]]] = []
for dic in array {
// get the `report_date` value from the original dictionary
let reportDate = dic["report_date"] ?? "there was no value for the `report_date`"
// create the new item that should be appended in the new array
var newDic: [String: [String: String]] = [:]
// set the key to the `reportDate`, and the value of that key to the original dictionary
newDic[reportDate] = dic
// append the new dictionary into the final array
finalArray.append(newDic)
}
print(finalArray)
}
still I would highly recommend that you change this to convert the original data into an dictionary of type [String: [String: String]] instead, which I belive is what you really intent to get:
// the array of the original data
let array = [dic1, dic2]
func sortThemOut() {
var finalDictionary: [String: [String: String]] = [:]
for dic in array {
// get the `report_date` value from the original dictionary
let reportDate = dic["report_date"] ?? "there was no value for the `report_date`"
// set the key to the `reportDate`, and the value of that key to the original dictionary
finalDictionary[reportDate] = dic
}
print(finalDictionary)
}
which you can use like this:
// key will represent the `report_date`, and the value will contain the rest of the object
for (key, value) in finalDictionary {
print("key: " + key)
print("value: \(value)")
// OUTPUT:
// key: 2020-03-27
// value: ["report_date": "2020-03-27", "estimate": "-0.12", "code": "AUTO.US", "difference": "<null>", "date": "2019-12-31", "actual": "<null>", "percent": "<null>"]
// key: 2020-03-30
// value: ["report_date": "2020-03-30", "estimate": "-0.5600000000000001", "date": "2019-12-31", "difference": "<null>", "percent": "<null>", "actual": "<null>", "code": "APTX.US"]
}
print(finalDictionary["2020-03-27"])
// ["report_date": "2020-03-27", "estimate": "-0.12", "code": "AUTO.US", "difference": "<null>", "date": "2019-12-31", "actual": "<null>", "percent": "<null>"]
UPDATE
Based on the clarifications in the comments below, the resulted object is going to be a dictionary of type `[String: [[String: String]]]', so the function would be:
func sortThemOut() {
var finalDictionary: [String: [[String: String]]] = [:]
for dic in array {
// get the `report_date` value from the original dictionary
let reportDate = dic["report_date"] ?? "there was no value for the `report_date`"
// creating an array that will hold the original objects
// try to get the array that was previously inserted (if a previous object with same report_date was inserted before, or assign an new empty array
var objects = finalDictionary[reportDate] ?? []
// append the object into the array
objects.append(dic)
// set the key to the `reportDate`, and the value of that key to the array of original objects
finalDictionary[reportDate] = objects
}
}

Traverse nsdictionary in swift

I am new to swift.
I have my dictionary as
monthData =
{
"2018-08-10" = {
accuracy = 71;
attempted = 7;
correct = 5;
reward = Bronze;
};
"2018-08-12" = {
accuracy = 13;
attempted = 15;
correct = 2;
reward = "";
};
"2018-08-13" = {
accuracy = 33;
attempted = 15;
correct = 5;
reward = "";
};
"2018-08-14" = {
accuracy = 100;
attempted = 15;
correct = 15;
reward = Gold;
};
"2018-08-16" = {
accuracy = 73;
attempted = 15;
correct = 11;
reward = Silver;
};
"2018-08-21" = {
accuracy = 26;
attempted = 15;
correct = 4;
reward = "";
};
"2018-08-23" = {
accuracy = 46;
attempted = 15;
correct = 7;
reward = "";
};
}
I want to get all the dates for which reward is Gold
Can anyone please help me do that?
What I have tried 'till now is:
for (key,value) in monthData{
let temp = monthData.value(forKey: key as! String) as! NSDictionary
for (key1,value1) in temp{
if((value1 as! String) == "Gold"){
print("keyFINAL \(key)")
}
}
but it outputs the error Could not cast value of type '__NSCFNumber' to 'NSString'
The error occurs because when you are iterating the dictionary you force cast the Int values to String which is not possible
The (highly) recommended Swift way is to use the filter function. This is much more efficient than a loop.
In the closure $0.1 represents the value of the current dictionary ($0.0 would be the key). The result is an array of the date strings.
let data : [String:Any] = ["monthData" : ["2018-08-10": ["accuracy" : 71, "attempted" ... ]]]
if let monthData = data["monthData"] as? [String:[String:Any]] {
let goldData = monthData.filter { $0.1["reward"] as? String == "Gold" }
let allDates = Array(goldData.keys)
print(allDates)
}
The code safely unwraps all optionals.
However if there is only one Gold entry the first function is still more efficient than filter
if let monthData = data["monthData"] as? [String:[String : Any]] {
if let goldData = monthData.first( where: {$0.1["reward"] as? String == "Gold" }) {
let goldDate = goldData.key
print(goldDate)
}
}
In Swift avoid the ObjC runtime (value(forKey:)) and Foundation collection types (NSDictionary) as much as possible.
From the first for in loop, you are getting the NSDictionary in temp variable
"2018-08-16" = {
accuracy = 73;
attempted = 15;
correct = 11;
reward = Silver;
};
So, you should directly check .value(forKey:) on temp and get the value for reward.
You should try it like this
for (key,value) in monthData {
let temp = monthData.value(forKey: key as! String) as! NSDictionary
if(((temp.value(forKey: "reward")) as! String) == "Gold"){
print("keyFINAL \(key)")
}
}
Try and share results
EDIT
Please checkout the answer from vadian for in-depth explanation and pure swift approach to achieve the same.
Thanks

How do I sort an NSDictionary by integer value?

I have a NSDictionary. When I print it, it looks like this:
{
3gZ0qtk0yMUEvtSmz2RW40Y7AC83 = 12;
USXhV0QYkpbSzAmXgfeteXHuoqg2 = 25;
UTPFBV5Q5IgTh17060c6WxNDQCO2 = 1;
fqsspZtskWVheqUQQtROepixsGB2 = 256;
}
I want to sort it so that it looks like this:
{
fqsspZtskWVheqUQQtROepixsGB2 = 256;
USXhV0QYkpbSzAmXgfeteXHuoqg2 = 25;
3gZ0qtk0yMUEvtSmz2RW40Y7AC83 = 12;
UTPFBV5Q5IgTh17060c6WxNDQCO2 = 1;
}
All of the methods I see needs a key name but I don't understand that. There is no key name. Sorry if this is duplicate but everything I try from other questions doesn't work. There's just too many ways out there and I get all kinds of errors and other problems so I want to know the best (latest), and most efficient way to sort NSDictionaries in this way.
Also it's coming from a firebase snapshot
example:
self.friendsDict = snapshot.value as! NSDictionary
try this -
let dict:NSMutableDictionary = [
"3gZ0qtk0yMUEvtSmz2RW40Y7AC83" : 12,
"USXhV0QYkpbSzAmXgfeteXHuoqg2" : 25,
"UTPFBV5Q5IgTh17060c6WxNDQCO2" : 1,
"fqsspZtskWVheqUQQtROepixsGB2" : 256
]
let sortedKeys2 = (dict as NSDictionary).keysSortedByValueUsingComparator
{
($1 as! NSNumber).compare($0 as! NSNumber)
}
OR
let dict = [
"3gZ0qtk0yMUEvtSmz2RW40Y7AC83" : 12,
"USXhV0QYkpbSzAmXgfeteXHuoqg2" : 25,
"UTPFBV5Q5IgTh17060c6WxNDQCO2" : 1,
"fqsspZtskWVheqUQQtROepixsGB2" : 256
]
var sortedKeys = Array(dict.keys).sort({dict[$0] > dict[$1]})
Since you said the solution from this link Swift sort dictionary by value didn't work, I am posting a working code on my side:
let dict:NSDictionary = [
"3gZ0qtk0yMUEvtSmz2RW40Y7AC83" : 12,
"USXhV0QYkpbSzAmXgfeteXHuoqg2" : 25,
"UTPFBV5Q5IgTh17060c6WxNDQCO2" : 1,
"fqsspZtskWVheqUQQtROepixsGB2" : 256
]
let d = dict as! [String:NSInteger]
for (k,v) in (Array(d).sort {$0.1 > $1.1}) {
print("\(k):\(v)")
}
Output:
fqsspZtskWVheqUQQtROepixsGB2:256
USXhV0QYkpbSzAmXgfeteXHuoqg2:25
3gZ0qtk0yMUEvtSmz2RW40Y7AC83:12
UTPFBV5Q5IgTh17060c6WxNDQCO2:1
Edit:
For above code to work, OP had to change:
self.friendsDict = snapshot.value as! NSDictionary
to:
self.friendsDict = snapshot.value as! [String:NSInteger]

NSDictionary annidate in swift

I have this json result.
I would take the field "alert".
I try this:
var alert: NSString = jsonResult["features"]["properties"]["alert"]
but this is the error: does not have a member named 'subscript'.
I can not how to access a field in a nested dictionary
{
features = (
{
geometry = {
coordinates = (
"-97.95359999999999",
"37.2382",
5
);
type = Point;
};
id = usb000si7g;
properties = {
alert = green;
cdi = "5.8";
code = b000si7g;
detail = "http://earthquake.usgs.gov/earthquakes/feed/v1.0/detail/usb000si7g.geojson";
dmin = "0.017";
felt = 1258;
gap = 38;
ids = ",usb000si7g,";
mag = "4.3";
magType = mwr;
mmi = "4.94";
net = us;
nst = "<null>";
place = "8km SE of Harper, Kansas";
rms = "0.51";
sig = 864;
sources = ",us,";
status = reviewed;
time = 1412272884590;
title = "M 4.3 - 8km SE of Harper, Kansas";
tsunami = "<null>";
type = earthquake;
types = ",cap,dyfi,general-link,geoserve,losspager,moment-tensor,nearby-cities,origin,phase-data,shakemap,tectonic-summary,";
tz = "-300";
updated = 1412614943519;
url = "http://earthquake.usgs.gov/earthquakes/eventpage/usb000si7g";
};
type = Feature;
}
);
metadata = {
api = "1.0.13";
count = 1;
generated = 1412617232000;
status = 200;
title = "USGS Significant Earthquakes, Past Week";
url = "http://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/significant_week.geojson";
};
type = FeatureCollection;
}
I don't know what to do. swift is changed every beta.
As #Kirsteins said is his answer, you have to do a lot of unwrapping, and it's recommended to use a JSON library.
If you still want to stick with hand made extraction, then I suggest you to made it programmatically, such as adding an extension to NSDictionary as follows:
extension NSDictionary {
func objectForTreeKeys<T>(keys:[String]) -> T? {
var dict: NSDictionary? = self
var retValue: T?
for key in keys {
var value: AnyObject? = dict?.objectForKey(key)
if let unwrapped = value as? NSDictionary {
dict = unwrapped
} else if let unwrapped = value as? T {
retValue = unwrapped
break
} else {
retValue = nil
break
}
}
return retValue
}
}
You pass an array of keys to the function, and it traverses all nested dictionaries until:
a value of type T is encountered
a value having type different than NSDictionary and T is found
a nil value is found
In the first case, it returns the value of T type - in the other cases it returns nil.
You can use it as follows:
let ret: String? = jsonResult.objectForTreeKeys(["features", "properties", "alert"])
As you can see, it's a generic method, and the return type is inferred from the type of the variable the result is assigned to - so it's necessary to explicitly define its type, which must be optional (String? in this specific case).

Resources