In my Swift app, I'm querying an api returning a json object like this :
{
key1: value1,
key2: value2,
array : [
{
id: 1,
string: "foobar"
},
{
id: 2,
string: "foobar"
}
]
}
Facts :
The array value can be empty.
I want to read the first array element,
present or not.
In Swift i'm doing :
if let myArray: NSArray = data["array"] as? NSArray {
if let element: NSDictionary = myArray[0] as? NSDictionary {
if let string: NSString = element["string"] as? NSString {
// i should finally be able to do smth here,
// after all this crazy ifs wrapping
}
}
}
It works if the array and the first element exist, but i'm having a crash with "index 0 beyond bounds for empty array" even if the element assignment is within an if let wrapping.
What i am doing wrong here ? I'm getting crazy with Swift optionals, typing and crazy if let wrapping everywhere...
The error is not concerning about Optional. If you use subscription([]) for array, you have to check the length of that.
if let myArray: NSArray = data["array"] as? NSArray {
if myArray.count > 0 { // <- HERE
if let element: NSDictionary = myArray[0] as? NSDictionary {
if let string: NSString = element["string"] as? NSString {
println(string)
}
}
}
}
But we have handy .firstObject property
The first object in the array. (read-only)
If the array is empty, returns nil.
Using this:
if let myArray: NSArray = data["array"] as? NSArray {
if let element: NSDictionary = myArray.firstObject as? NSDictionary {
if let string: NSString = element["string"] as? NSString {
println(string)
}
}
}
And, we can use "Optional Chaining" syntax:
if let str = (data["array"]?.firstObject)?["string"] as? NSString {
println(str)
}
You can use this
var a = [Any?]()
a.append(nil)
If you have a non-optional array with AnyObject you can use NSNull (like in Obj-C)
Related
What I need is how we need to filter NSDictionary and return only the values and the keys where the key contain a string
For example if we have NSDictionary that contain :
{
"houssam" : 3,
"houss" : 2,
"other" : 5
}
and the string is "houss"
so we need to return
{
"houssam" : 3,
"houss" : 2
}
Best Regards,
You can use the filter function to get what you need like in the following way:
var dict: NSDictionary = ["houssam": 3, "houss": 2, "other": 5 ]
let string = "houss"
var result = dict.filter { $0.0.containsString(string)}
print(result) //[("houssam", 3), ("houss", 2)]
The above code return a list of tuples, if you want to get a [String: Int] dictionary again you can use the following code:
var newData = [String: Int]()
for x in result {
newData[x.0 as! String] = x.1 as? Int
}
print(newData) //["houssam": 3, "houss": 2]
I hope this help you.
Use this code to get matching keys.
var predicate = NSPredicate(format: "SELF like %#", "houss");
let matchingKeys = dictionary.keys.filter { predicate.evaluateWithObject($0) };
Then just fetch entries which keys are in matchingKeys array.
Since this is an NSDictionary, you can use the filteredArrayUsingPredicate: method on the array of keys, and fetch back the values from the initial dictionary.
For instance:
let data: NSDictionary = // Your NSDictionary
let keys: NSArray = data.allKeys
let filteredKeys: [String] = keys.filteredArrayUsingPredicate(NSPredicate(format: "SELF CONTAINS[cd] %#", "houss")) as! [String]
let filteredDictionary = data.dictionaryWithValuesForKeys(filteredKeys)
Hope that helps.
I have an application that is using the Foursquare API to download JSON data. I am using NSURLSession and the dataTaskWithRequest with completion block method to fetch the data. I am getting the data fine but sometimes a nested array named groups can be empty. And when I am parsing through the JSON like below, for some reason my conditional statement is not handling the empty array like I am expecting it should. Instead of evaluating the array as empty and proceeding to the "else" portion of the if let...else statement if instead throughs a runtime error stating: index 0 beyond bounds of empty array
if let response: NSDictionary = data["response"] as? [String: AnyObject],
groups: NSArray = response["groups"] as? NSArray,
// Error here \|/ (sometimes groups array is empty)
dic: NSDictionary = groups[0] as? NSDictionary,
items: NSArray = dic["items"] as! NSArray {
}
else {
// Never gets here. Why?
// IF the groups array is empty, then the if let should return
// false and drop down to the else block, right?
}
I am relatively new to Swift, can someone tell me why this is happening and what I can do to fix this? Thanks
You have to check explicitly outside an if letstatement if the array is empty, because
An empty array is never an optional
if let response = data["response"] as? [String: AnyObject], groups = response["groups"] as? NSArray {
if !groups.isEmpty {
if let dic = groups[0] as? NSDictionary {
items = dic["items"] as! NSArray
// do something with items
println(items)
}
}
} else ...
You can omit all type annotations while downcasting a type
However, you can perform the check with a where clause, this works in Swift 1.2 and 2
if let response = data["response"] as? [String: AnyObject],
groups = response["groups"] as? [AnyObject] where !groups.isEmpty,
let dic = groups[0] as? NSDictionary,
items = dic["items"] as? NSArray {
// do something with items
println(items)
} else {...
Use && operator between statments.
For example this won't crash:
var a = 4;
var c = 5;
var array = Array<Int>();
if a > 2 && c > 10 && array[0] != 0 {
}
Make sure the array is not empty before trying to access it:
if let response: NSDictionary = data["response"] as? [String: AnyObject],
let groups: NSArray = response["groups"] as? NSArray where groups.count > 0 {
if let dic: NSDictionary = groups[0] as? NSDictionary,
let items: NSArray = dic["items"] as? NSArray {
// Do something..
return // When the 2nd if fails, we never hit the else clause
}
}
// Do logic for else here
...
I have been working with an array of dictionaries in Swift (version 7 beta 4) using NSDictionary. The array is a collection of fmdb results which contain differing data types. I would like to end up with an array of native swift dictionaries in order to use the filter functionality of swift collection types. The following is a snippet of the query function I'm using the create the array:
class func query(sql:String) -> [NSDictionary] {
let dirPaths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory,.UserDomainMask, true)
let docsDir = dirPaths[0] as String
let databasePath:String = docsDir.stringByAppendingPathComponent("myDB.db")
let db = FMDatabase(path: databasePath as String)
var resultsArray:[NSDictionary] = []
if db.open() {
let results:FMResultSet? = db.executeQuery(sql, withArgumentsInArray: nil)
while (results?.next() == true) {
resultsArray.append(results!.resultDictionary()) //appending query result
}
db.close()
} else {
print("Error: \(db.lastErrorMessage())")
}
return resultsArray
}
I tried using AnyObject such as:
class func query(sql:String) -> [[String:AnyObject]] {
let dirPaths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory,.UserDomainMask, true)
let docsDir = dirPaths[0] as String
let databasePath:String = docsDir.stringByAppendingPathComponent("Kerbal.db")
let db = FMDatabase(path: databasePath as String)
var resultsArray:[[String:AnyObject]] = []
if db.open() {
let results:FMResultSet? = db.executeQuery(sql, withArgumentsInArray: nil)
while (results?.next() == true) {
resultsArray.append(results!.resultDictionary() as! Dictionary<String,AnyObject>)
}
db.close()
} else {
print("Error: \(db.lastErrorMessage())")
}
return resultsArray
}
however I can't use the filter such as resultsArray.filter({$0["fieldName"] == "part"}) with AnyObject.
My question: Is it possible to create this array with native Swift dictionaries even though the dictionaries have different types? Could the new protocol extension be used on collection type to solve this problem?
Any suggestions are appreciated.
If you don't know which objects you are dealing with you have to use filter like so:
resultsArray.filter{ ($0["fieldName"] as? String) == "part"}
So you optionally cast the value to the desired type and compare it.
Note: I'm using the trailing closure syntax.
As suggestion I would use a tuple of different arrays which hold dictionaries:
// some dummy values
let resultsArray: [[String : AnyObject]] = [["Hi" : 3], ["Oh" : "String"]]
var result = (doubles: [[String : Double]](), strings: [[String : String]]())
// going through all dictionaries of type [String : AnyObject]
for dict in resultsArray {
// using switch and pattern matching to cast them (extensible for more types)
// you have to up cast the dictioanary in order to use patten matching
switch dict as AnyObject {
case let d as [String : Double]: result.doubles.append(d)
case let s as [String : String]: result.strings.append(s)
default: fatalError("unexpected type")
}
}
return result
You should probably adopt the Equatable protocol, not all AnyObjects are such in fact, and so no comparison and thereafter filtering may be done.
I made a UITableview with NSMutableArray, having data downloaded from server in form of JSON. When I perform the table cell the code goes like below.
if let rstrntName = self.items[indexPath.row]["rstrnt_name"] as? NSString {
cell.rstrntName.text = rstrntName
}
Now I want to sort it by a column named "rstrnt_name". Below is the code I tried, but it doesn't work.
self.items.sortedArrayUsingComparator({obj1, obj2 -> NSComparisonResult in
let rstrnt1: NSDictionary = obj1 as NSDictionary
let rstrnt2: NSDictionary = obj2 as NSDictionary
if (rstrnt1["rstrnt_name"] as String) < (rstrnt2["rstrnt_name"] as String) {
return NSComparisonResult.OrderedAscending
}
if (rstrnt1["rstrnt_name"] as String) > (rstrnt2["rstrnt_name"] as String) {
return NSComparisonResult.OrderedDescending
}
return NSComparisonResult.OrderedSame
})
How can I sort objects in such type?
Assign the sorted array to anywhere and check -
self.items = self.items.sortedArrayUsingComparator(//rest of your code
should give you the sorted result.
self.items.sortedArrayUsingComparator returns a sorted array which you appear to be throwing away to the ether. Try storing the value instead.
Here is swift Array types sort method with pattern matching. This sort method directly mutates items doesn't return new one.
var items: [[String: AnyObject]] = [["rstrnt_name": "mustafa"], ["rstrnt_name": "Ray"], ["rstrnt_name": "Ali"]]
items.sort { (left, right) -> Bool in
let first = left["rstrnt_name"] as? String
let second = right["rstrnt_name"] as? String
switch (first, second) {
case let (.Some(x), .Some(y)): return x < y
default: return false
}
}
I'm tring to parse a JSON format like this:
{
"key_1" : {
"key_2" : "value"
}
}
and then assign "value" to a variable.
Here is my code:
var variableShouldBeAssigned: String
if let x = (jsonResult["key_1"]? as? NSDictionary) {
if let y = (x["key_2"]? as? String) {
variableShouldBeAssigned = y
}
}
However, an error occurs when I try to downcast from x["key_2"]? to a String, but it's fine to downcast from jsonResult["key_1"]? to an NSDictionary.
I can solve the error by using x["key_2"] to replace x["key_2"]?, but I don't really know why it only works for jsonResult["key_1"]?.
Can anybody tell me the reason?
String does not conform to NSCopying, but surely NSString does!
Also, going from NSString to String is instantaneously implied...
So I would say try something like this... Change String to NSString
here is a sample, assuming that you handle the jsonResult as a NSDictionary...
func giveDictionary(jsonResult:NSDictionary) -> String?
{
if let x = (jsonResult["key_1"]? as? NSDictionary)
{
if let y = (x["key_2"]? as? NSString)
{
return y
}
}
return nil
}
You can simplify all your type checking by using a Swift dictionary at the beginning:
var variableShouldBeAssigned: String
if let dict = jsonResult as? [String:[String:String]] {
if let key1Dict = dict["key_1"] {
if let value = key1Dict["key_2"] {
variableShouldBeAssigned = value
}
}
}
In fact, you can even combine the two last if statements:
var variableShouldBeAssigned: String
if let dict = jsonResult as? [String:[String:String]] {
if let value = dict["key_1"]?["key_2"] {
variableShouldBeAssigned = value
}
}
In general, you should using Swift Dictionaries instead of NSDictionary