Mapping from array of dictionaries to array of numbers in Swift - ios

Here is a function that should turn an an array of key-value pairs for orders NSArray containing Dictionary<String, Any> to an array of IDs for each order ([NSNumber]).
However, I am still having problem with the type conversion, the error:
Type 'Any' has no subscript members
How to perform the mapping cleanly in Swift?
#objc static func ordersLoaded(notification:Notification) -> [NSNumber] {
// Function receives a Notification object from Objective C
let userInfo:Dictionary = notification.userInfo as! Dictionary<String, Any>
// orders is an array of key-value pairs for each order Dictionary<String,Any>
let ordersWithKeyValuePairs:NSArray = userInfo["orders"] as! NSArray // Here a typed array of Dictionaries would be preferred
// it needs to be simplified to an array of IDs for each order (NSNumber)
// orderID is one of the keys
let orderIDs:[NSNumber];
orderIDs = ordersWithKeyValuePairs.flatMap({$0["orderID"] as? NSNumber}) // Line with the error
/*
orderIDs = ordersWithKeyValuePairs.map({
(key,value) in
if key==["orderID"] {
return value
} else {
return nil
}
}) as! [NSNumber]
*/
return orderIDs
}

You can try this
if let ordersWithKeyValuePairs = userInfo["orders"] as? [[String:Any]] {
let result = ordersWithKeyValuePairs.compactMap{$0["orderID"] as? Int }
}

Here is what worked, casting ordersWithKeyValuePairs to [Dictionary<String, Any>] solved the problem to me:
#objc static func ordersLoaded(notification:Notification) -> [NSNumber] {
// Function receives a Notification object from Objective C
let userInfo:Dictionary = notification.userInfo as! Dictionary<String, Any>
// orders is an array of key-value pairs for each order Dictionary<String,Any>
let ordersWithKeyValuePairs:[Dictionary<String, Any>] = userInfo["orders"] as! [Dictionary<String, Any>]
// it needs to be simplified to an array of IDs for each order (NSNumber)
// orderID is one of the keys
let orderIDs:[NSNumber];
orderIDs = ordersWithKeyValuePairs.flatMap({$0["orderID"] as? NSNumber}) // Line with the error
return orderIDs
}

Related

Cannot subscript a value of type '[[String : Any]]' with an index of type 'String'

I'm trying to extract the info from a json array and i'm getting this error
"Cannot subscript a value of type '[[String : Any]]' with an index of type 'String'"
here
if let rev = place.details?["reviews"] as? [[String:Any]] {
if let ver = rev["author_name"] as? String { // <- IN THIS LINE I GET THE ERROR
}
}
i know that if i cast the type as [String : Any] instead of [[String:Any]] it will work, but in this case i have to cast it as an array of arrays otherwise it doesn't read the json, so how can i solve the problem?
[[String:Any]] is an array. It can be only subscripted by Int index.
You have to iterate over the array for example:
if let reviews = place.details?["reviews"] as? [[String:Any]] {
for review in reviews {
if let authorName = review["author_name"] as? String {
// do something with authorName
}
}
}
You can't access an item in an array with a String. You have to use Int
[[String:Any]] This is an array of dictionaries.
[[String:Any]] is a two dimensional Array. It can be only subscripted using Int index.
It is better to use a forEach loop, e.g.
if let reviews = place.details?["reviews"] as? [[String:Any]] {
reviews?.forEach { review in
if let authorName = review["author_name"] as? String {
// do something with authorName
}
}
}
I think you are mixing up dictionaries and arrays here.
If you want to access an element in an array, you have to use an Int index like this
let a = ["test", "some", "more"] // your array
let b = a[0] // print(b) = "test"
If you want to access an element in a dictionary, you can access it via its key, in your case a String
let dict: [String: Any] = ["aKey": "someValue"]
let value = dict["aKey"] // print(value) = "someValue"
In your case you have an array of dictionaries, each of them containing information about a review. If you want to access the author of one of your reviews, you have to first get the review dictionary out of your array like this:
if let reviews = place.details?["reviews"] as? [[String:Any]],
let review = reviews[0] {
// here you can access the author of the review then:
if let author = review["author_name"] as? String {
// do something
}
}
Instead of accessing just the first review like in my example, you can also loop via the array to access all of the reviews one by one

Cannot convert value of type 'NSDictionary.Iterator.Element' (aka '(key: Any, value: Any)') to expected argument type 'NSDictionary'

I am facing error on 7th line it's giving "Cannot convert value of type'NSDictionary.Iterator.Element' (aka '(key: Any, value: Any)') to expected argument type 'NSDictionary'".
init(snapshot: FIRDataSnapshot) {
key = snapshot.key
ref = snapshot.ref
let snapshotValue = snapshot.value as! [String: AnyObject]
if (snapshotValue["visitor"] != nil) {
for item in snapshotValue["visitor"] as! NSDictionary {
visitor = UserVisitor.init(visitorData: item)
}
}
snapshotValue["visitor"] is obviously an array so cast it to Swift Array containing dictionaries:
if let visitors = snapshotValue["visitor"] as? [[String:Any]] {
for item in visitors {
visitor = UserVisitor(visitorData: item)
}
}
Try a pure Swift approach like this :
// snapshotValue is a dictionary
if let snapshotValue = snapshot.value as? [String:Any] {
// The content of the "visitor" key is a dictionary of dictionaries
if let visitorDictionary = snapshotValue["visitor"] as? [String:[String:Any]] {
for (visitorId, visitorData) in visitorDictionary {
// This assumes that your initializer for UserVisitor is
// init(visitorData: [String:Any]) { ... }
visitor = UserVisitor.init(visitorData: visitorData)
}
}
}
Try not using NSDictionary or NSArray unless you really need to. Native Swift types work better in Swift...
You can write the else statements to handle data that is not in the expected format.

Extract value from dictionary of annoying format

I apologise for the title of this question. I have no idea what else to call it.
So... When calling the following:
let testData: [NSObject : AnyObject] = getTestData()
print(testData)
I get this output:
[data: {"TypeId":7,"DataList":null,"TypeName":"This is a test"}]
How would I be able to access the value 7 for the key "TypeId"?
EDIT:
Please note that it's holding { } brackets, not only [ ], thus a cast to NSDictionary is not possible as far as I have tried.
Kind regards,
Anders
You can achieve plist-like nested structures using Any type for dictionary values which is Swift's somewhat counterpart to Objective-C's id type but can also hold value types.
var response = Dictionary()
response["user"] = ["Login": "Power Ranger", "Password": "Mighty Morfin'"]
response["status"] = 200
Any seems to be better than AnyObject because in the above code response["status"] is of type Swift.Int, while using value type of AnyObject it is __NSCFNumber.
The way most people do it is to parse annoying JSON data as custom objects. That should be done as soon as you get the JSON. Ideally, data as JSON should not be used outside your communication code, example:
First, let's define a class to hold your server data:
class MyServerObject {
let typeId: Int
let typeName: String
let dataList: [AnyObject]?
init(dictionary: Dictionary<String, AnyObject>) {
let dataDictionary = dictionary["data"] as! Dictionary<String, AnyObject>
self.typeId = dataDictionary["TypeId"] as! Int
self.typeName = dataDictionary["TypeName"] as! String
self.dataList = dataDictionary["DataList"] as? [AnyObject]
}
}
Note that init method is already parsing the JSON. This doesn't have to be done in init, you could also create a static parse method that will return a new instance.
Usage:
// demo data
let jsonString = "{\"data\": {\"TypeId\":7,\"DataList\":null,\"TypeName\":\"This is a test\"}}"
let jsonData = jsonString.dataUsingEncoding(NSUTF8StringEncoding)!
let json = try! NSJSONSerialization.JSONObjectWithData(jsonData, options: [])
// parsing
let myServerObject = MyServerObject(dictionary: json as! Dictionary<String, AnyObject>)
// now we can simply read data as properties
print(myServerObject.typeId)
print(myServerObject.typeName)
One of the good thing about this solution is that we can check the JSON format and all the properties are parsed with the correct types.
Parsing can be hierarchical, for example, if your dataList contains complex objects, let's call them DataListItem, your parsing method can parse each item separately and put them into a [DataListItem], e.g.
if let dataListJSON = dataDictionary["DataList"] as? [Dictionary<String, AnyObject>] {
self.dataList = dataListJSON.map({ DataListItem($0) })
}
Also note that when parsing as! will crash the app when the format is invalid. as? will return nil if the types don't match. as? is very useful for types that can be nil because they are parsed as NSNull instances.
taking in account your data ...
print(testData)
/*
[data: {
DataList = null;
TypeId = 7;
TypeName = "This is a test";
}]
*/
// DataList type should be declared somewhere
class DataList {}
// parse data or set default value, if 'key' doesn't exist
if let data = testData["data"] as? [String:AnyObject] {
let dataList = data["DataList"] as? DataList // nil
let typeId = data["TypeId"] as? Int ?? 0 // 7
let typeName = data["TypeName"] as? String ?? "" // This is test
}

Issue displaying array of image from url

Through Firebase I'm calling many url to convert it to UIImage but aren't displayed in a corrected order. The print func stamp to the consolle the current index:
0 2 1 3 4
The JSON is like:
{
"evento_1" = "http://.altervista.org/evento1.jpeg";
"evento_2" = "http://.altervista.org/evento2.jpeg";
"evento_3" = "http://.altervista.org/evento3.jpeg";
"evento_4" = "http://.altervista.org/evento4.jpeg";
"evento_5" = "http://.altervista.org/evento5.jpeg";
}
Function to get the data:
ref.observeEventType(.Value, withBlock: { snapshot in
let nsDictionary = snapshot.value as? NSDictionary
for (key, value) in nsDictionary! {
dict[key as! String] = value
}
var index = 0
for (_, url ) in dict {
self.loadImage(url as! String, i: index)
index++
}
}, withCancelBlock: { error in
print(error.description)
})
Image Loader func:
func loadImage(urlString:String,i:Int)
{
let url = NSURL(string: urlString)
let data = NSData(contentsOfURL: url!)
self.eventi[i].image = UIImage(data: data!)
}
Put your Firebase snapshot keys into an array and the key:value pairs into a dictionary. Then sort the key array:
arrayOfKeys = Array(dict.keys) //swift
arrayOfKeys.sort {
return $0 < $1
}
then you can iterate over the array to get the event name (the key), which corresponds to the objects in your dictionary (access it by key) which returns it's value.
or (and I like this better)
Just take every .value dictionary and put each into an array then sort the array by the eventName (assuming that's the key)
eventArray.sort({ $0.eventName > $1.eventName })
As Jay and Eric mention: dictionaries have no defined order. The FDataSnapshot does have a defined order, but once you convert it to a dictionary, you're throwing that away.
So instead of first converting to a dictionary and then looping, you should simply loop over the children of the snapshot with:
for child in snapshot.children {
let key = child.key as String
print(key);
}
Where I print the key, you can do whatever you need with the child snapshot.

error using valueForKey in swift

Why do I get an error when I used valueForKey... I am using same trick like in objectiveC ...
In ObjectiveC, the code is
self.strSubscribe =[responseObject[#"subscribe"] valueForKey:#"subscribe_ids"];
In Swift , the code is
self.strSubscribe = responseObject["subscribe"].valueForKey["subscribe_ids"] as! String
I declare the variables like
var arraySubCategory : NSMutableArray! = NSMutableArray()
var strSubscribe:String!
And I tried to access the value from below response
{
subscribe =
{
"subscribe_ids" = "1,14";
}
}
Edit
It works using Amit and Eric's solution but now for following data
{
data = (
{
"subscribe_ids" = "1,14";
}
);
}
let dictionary = responseObject["data"][0] as! Dictionary<String,AnyObject>
self.strSubscribe = dictionary["subscribe_ids"] as! String
OR//
if let dic = responseObject["data"][0] as? [String:String], let ids = dic["subscribe_ids"] {
self.strSubscribe = ids
}
but it gives me error:
could not find member 'subscript'
Swift doesn't know the type of responseObject["subscribe"], you have to help the compiler a bit; for example:
if let dic = responseObject["subscribe"] as? [String:String], let ids = dic["subscribe_ids"] {
self.strSubscribe = ids // "1,14"
}
UPDATE:
It's still the same problem: the compiler doesn't know the type of responseObject["data"], so when you try to access the subscript there's an error (because you know it's a dictionary inside the array, but the compiler doesn't).
One solution is to give the type to the compiler by declaring an array of dictionaries in the if let condition:
if let arr = responseObject["data"] as? [[String:String]], let ids = arr[0]["subscribe_ids"] {
self.strSubscribe = ids
}
Notice that it's [[String:String]] (array of dictionaries), not [String:String] (dictionary).
Write like this.
let dictionary = responseObject["subscribe"] as! Dictionary<String, AnyObject>
self.strSubscribe = dictionary["subscribe_ids"] as! String
Since responseObject["subscribe"] will give a AnyObject? output and AnyObject does not have any member called valueForKey.

Resources