array count error swift - ios

I have searched long, but couldn't find a solution for my bug. Swift somehow doesn't count my array (converted from json) correctly. This is the code I use to create the array:
let jsonData = NSData(contentsOfURL: url)
let jsonDic = NSJSONSerialization.JSONObjectWithData(jsonData, options: NSJSONReadingOptions.MutableContainers, error: &error) as NSDictionary
var count = jsonDic.count
When the count should be 3, the count is 2. So I just always added 1, but now if the count should be 4, the count is still 2.
Has anyone experienced something like that or is it just me doing something wrong?
EDIT: This is an example input:
{"items":[{"var1":"xxx","var2":"xxx","var3":"xxx","var4":"xxx","var5":0},{"var1":"xxx","var2":"xxx","var3":"xxx","var4":"xxx","var5":0}, {"var1":"xxx","var2":"xxx","var3":"xxx","var4":"xxx","var5":0}]}

The sample data you posted is a dictionary with one items key, and the corresponding value is an array (so the dictionary count should be 1).
By using this code:
let array = jsonDic["items"] as? NSArray
array?.count
I see that that array has 3 elements.
If what you are trying to count is the array, then I would use the above code, or this one using optional binding:
if let array = jsonDic["items"] as? NSArray {
array.count
}
NOTE: I'd warn you about using jsonDic["items"]!.count because it is not safe: if the items key is not in the dictionary, or if its value cannot be cast to an array, then a run time exception will be thrown.

Related

Swift NSDictionary get 0th value without knowing key name

I have an NSDictionary that has a key like messageID5 and the value has three key/value pairs.
I know the NSDictionary only has 1 value in it because I limited my query to 1. But I don't know the name of the key. I just want the value, but I can't access it like an array [0]. You can access it just fine in PHP or Python. I've been trying a lot of different solutions for this basic problem, but a lot of them seem overly messy. anyValue[0] gives me a type error.
If you don't know your dictionary keys, you can get your NSDictionary allKeys.first property or allValues.first:
let dict = NSDictionary(dictionary: ["a":["b":1]])
let subDict = dict[dict.allKeys.first] as? [String:Any] ?? [:] // ["b": 1]
// or
let subDict = dict.allValues.first as? [String:Any] ?? [:] // ["b": 1]
The first thing to acknowledge is that key/value pairs in dictionaries does not maintain any specific order - this is required for an optimization in access to the contents of this structure.
As for your case if you're 100% sure you'll have only one value inside your dictionary you can use .allValues.first to retrieve the contained value. If your know that the type of your value is NSDictionary the whole code may look like this:
let childDictionary = rootDictionary.allValues.first as? NSDictionary
I suggest using (dictionary as Dictionary).values.first. That returns an optional, since it can fail if the dictionary is empty.
(Note that I edited this answer to cast the dictionary from an NSDictionary to a Dictionary so you an use the values property. NSDictionary doesn't have a values property, but Dictionary does.)

Can't cast from Dictionary value to Array

No idea why this won't work. The value of this dictionary is a key, yet whenever I try to cast to an array, I get a multitude of errors. Here's what I've tried, with the error I get following each example (in all examples, parameters is type [String : Any]:
let paramsArray = parameters["inputVO"] as AnyObject
if let array = paramsArray as? Array {
}
Error: Generic parameter 'Element' could not be inferred in cast to 'Array<_>'
if let array = parameters["inputVO"] as? Array {
}
Error: Ambiguous reference to member 'subscript'
I'm not sure what else to do to cast the result to an array? I'm sure I've done this before, I have no idea why this is failing. Any help is greatly appreciated.
Edit: Here's the output when I print out params. As expected, it is populated with an Array of Dictionary's.
Optional([["stmtDate": cmd, "transId": identifier, "isSupplementDataAvailable": true]])
Either it's an array
if let array = parameters["inputVO"] as? [[String:Any]] { ... }
or a dictionary
if let dictionary = parameters["inputVO"] as? [String:Any] { ... }
Both types are generics and need specific type information
[[String:Any]] is the short form of Array<Dictionary<String,Any>>
[String:Any] is the short form of Dictionary<String,Any>

iOS 9 JSON Parsing loop

I'm creating an app that should retrieve some JSON from a database.
This is how my JSON looks:
[{"id":"1","longitude":"10","latitude":"10","visibility":"5","timestampAdded":"2015-10-01 15:01:39"},{"id":"2","longitude":"15","latitude":"15","visibility":"5","timestampAdded":"2015-10-01 15:06:25"}]
And this is the code i use:
if let jsonResult = JSON as? Array<Dictionary<String,String>> {
let longitudeValue = jsonResult[0]["longitude"]
let latitudeValue = jsonResult[0]["latitude"]
let visibilityValue = jsonResult[0]["visibility"]
print(longitudeValue!)
print(latitudeValue!)
print(visibilityValue!)
}
As you can see it only gets the first chunk from the JSON and if there are no JSON at all it will crash, but if i want it to count the amount and make an array out of it like this:
var longitudeArray = [10, 15]
var latitudeArray = [10, 15]
And so on...
I also need this to be apple watch compatible so i can't use SwiftyJSON.
What do i do? I really hope you can help me!
Thanks.
SOLVED!
Problems was solved by "Eric D."
This is the code:
do {
if let url = NSURL(string: "YOU URL HERE"),
let data = NSData(contentsOfURL: url),
let jsonResult = try NSJSONSerialization.JSONObjectWithData(data, options: []) as? [[String:AnyObject]] {
print(jsonResult)
let longitudeArray = jsonResult.flatMap { $0["longitude"] as? String }
let latitudeArray = jsonResult.flatMap { $0["latitude"] as? String }
print(longitudeArray)
print(latitudeArray)
}
} catch let error as NSError {
print(error.description)
}
Thank you soo much Eric!! :-)
You could use flatMap to get an array of your elements:
let longitudeArray = jsonResult.flatMap { $0["longitude"] as? String }
let latitudeArray = jsonResult.flatMap { $0["latitude"] as? String }
etc.
flatMap is like map but unwraps optionals, which is adequate because we need to safely cast the type of the object we get from each dictionary in the json array.
$0 represents the object in the current iteration of flatMap of the array it's applied to.
If you're currently using SwiftyJSON, then that would be:
let longitudeArray = jsonResult.flatMap { $1["longitude"].string }
let latitudeArray = jsonResult.flatMap { $1["latitude"].string }
because .string is SwiftyJSON's optional String value getter.
But as you said, you don't want to use it (anymore), so you need to use NSJSONSerialization to decode your JSON data, there's plenty of examples on the Web and on SO. Then you will be able to use my original answer.
You're already getting an array with all of the elements (not just the first one. you're simply only accessing the first one). jsonResult is an array of dictionaries. Each dictionary (in this case, based on the json you provided) contains these elements: id, longitude, latitude, visibility and timestampAdded. In order to access each of them, you can simply loop over jsonResult and access the i'th element (and not always the 0 element). This will also prevent the crash you're experiencing with the json is blank or invalid (since you'll only be going over the valid elements in jsonResult.
This will give you the flexibility to create the custom arrays you wish to create (in order to create an array of all of the longitudes, for example, you will simply add that element to the new array while looping over jsonResult). However, if you'd like to save yourself the trouble of manually building these arrays and assuming you have control over the json structure, I would recommend changing the received json to the relevant structure (a dictionary or arrays instead of an array of dictionaries), so it would better fit your needs and provide you the results in the relevant format right "out of the box".

Swift Subscript Error

I believe it has something to do with optionals, but I'm safely unwrapping sourceURL so I'm still not sure where the error is! I'm trying to access a JSON object's array's dictionary value.
However, I'm still getting the "could not find overload for 'subscript' that accepts the supplied arguments.
It seems simple, but I just can't seem to figure it out!
var dictTemp: NSDictionary! = NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers, error: &localError) as? NSDictionary
var finalURL: String
// error line below
if let sourceURL = dictTemp[0]["source"]["sourceUrl"] as? NSString {
finalURL = sourceURL as String
}
NSDictionary accessed from Swift is an interesting beast.
As long as Swift only knows something is an NSDictionary (not a more specific [Key: Value] Swift-style dictionary), you can only retrieve AnyObject?s out of it.
let dictTemp: NSDictionary = // from somewhere...
let step1 = dictTemp[0] // step1 is an AnyObject?
But then, since you've imported Foundation, you can keep going with a magical subscript operator that works on AnyObject, and checks whether the thing is a dictionary:
let step2 = step1?["source"] // step2 is any AnyObject??
Here's where it gets interesting, because
if step1 was a dictionary with a "source" key inside it, step2 will be the corresponding value.
if step1 was a dictionary without a "source" key, step2 will be nil — in particular, it's AnyObject??.Some(AnyObject?.None).
if step1 was nil (the original dictionary didn't have 0 as a key), or not a dictionary (it had a 0 key with some other kind of value), then step2 will be nil — in particular, AnyObject??.None.
(The distinction between the last 2 cases is mostly unimportant and you shouldn't worry about it, but if you're interested you can see it by using dump).
And of course, we can apply the same principle again:
let step3 = step2??["sourceUrl"] // step3 is AnyObject?? again
Now, binding them all in one if:
if let url = dictTemp[0]?["source"]??["sourceUrl"] as? String {
// do something with url...
}
Caveat
This type of syntax can be dangerous, since it works with arrays and dictionaries at the same time. What would you expect in these situations?
let dict: NSDictionary = [0: ["source": [3: "result"]]]
dict[0]?["source"]??[3] // returns nil (surprise!)
dict[0]?["source"]??[3 as NSNumber] // returns "result"
let dict2: NSDictionary = [0: ["source": [8, 7, 6, 5, 4]]]
dict2[0]?["source"]??[3] // returns 5
dict2[0]?["source"]??[3 as NSNumber] // returns nil (surprise!)

Looping an array of NSDictionaries in swift

Ok, trying to catch the last train to learn Swift, I have seen similar questions but I am not getting them to solve my issue.
I have an NSDictionary called entries, and one of the values, corresponding to key "TYPES" is an NSArray of NSDictionaries. I am trying to loop over this latter NSDictionary and retrieve an integer value for the key "TID", I am doing:
for dict in entries["TYPES"] as NSDictionary {
let tid : Int = typeDict["TID"]
}
But I am receiving as error: (key: AnyObject, value: AnyObject) does not have a member named 'subscript'
I understand this is due to entries["TYPES"] being anyObject! and comes from Xcode 6 beta 6, where a large number of Foundation APIs have been audited for optional conformance and hence need unwrapping but I have tried my best to unwrap without success, the compiler is always complaining a different thing. Someone knows how to do this?
If this is a sample of your dictionary:
var entries: NSDictionary = [
"TYPES": [
[],
["TPD": 2],
["TID": 4]
] as NSArray
]
you have to:
retrieve the element identified by the TYPES key, and attempt to cast as NSArray
loop through all elements of the array
attempt a cast of each element as NSDictionary
check for the TID key existence, and read its value
if the value is not nil, the search is over
This is the code:
var tid: Int?
if let types = entries["TYPES"] as? NSArray {
for type in types {
if let dict = types.lastObject as? NSDictionary {
tid = dict["TID"] as? Int
if tid != nil {
break
}
}
}
}
Running the code in a playground with the sample data, the output I see is {Some 4}.
However I would keep #Zaph's advice into account and model your data in a different way, using structs and/or classes

Resources