I'm looking for a straightforward way of converting a dictionary of type [String : Any?] to a dictionary of [String: AnyObject]. I could loop through the elements individually, but that just seems 'wrong' to me.
If I just try to cast the original dictionary, I get a crash.
let dict:[String : Any?] = ["something" : "else"]
// crashes with fatal error: 'can't unsafeBitCast
// between types of different sizes'
let newDict = dict as? [String: AnyObject]
Looping is exactly correct, and Swift encourages this. The most efficient and Swift-like (*) approach is:
var result = [String: AnyObject]()
for (key, value) in dict {
if let v = value as? AnyObject {
result[key] = v
}
}
(*) This isn't really "Swift-like" because it includes AnyObject, which should almost never be part of a non-temporary data structure. A proper Swift implementation would convert AnyObject to a real type.
Note that the crash you get is definitely not appropriate and you should open a radar (bugreport.apple.com). An as? should never crash like this. You should either get nil or a compiler failure.
Warning: as #rob-napier mentioned in the comments, its O(n^2) so this approach is only valid for small dictionaries (less than 100 elements).
You can use reduce:
let dict:[String : Any?] = ["something" : "else"]
let newDict = dict.reduce([String: AnyObject](), combine: { accumulator, pair in
var temp = accumulator
temp[pair.0] = pair.1 as? AnyObject
return temp
})
Here newDict is of type [String: AnyObject].
Related
I'm new to Swift and ObjC and can't get this simple bit of code to work. I get the error 'String?' is not convertible to 'String'; did you mean to use 'as!' to force downcast?
guard let data = context as? Dictionary<String,String> else {return}
var str : String
str = data["Score"] as String //<<<I get the error here
When I change it to as! I know get this warning: Forced cast from 'String?' to 'String' only unwraps optionals; did you mean to use '!'?
Any ideas how to extract the string from the dictionary so I can use it?
It might be possible, that the key "Score" is not set in your dictionary and may return nil. Use this code to unwrap the optional:
if let score = data["Score"] {
str = score
}
Since you already unwrapped context to Dictionary<String, String>, Swift will automatically infer the type of score as String.
You don't need to cast because data dictionary would always return optional String type and you can use guard statement
guard let str = data[Score] else { return }
// Your code here
The type returned by subscripting a Dictionary is an optional, since it is valid to subscript it with non-existinent keys. So, as per the error message, in a dictionary with String values, the return type is String?.
If you know for sure that the key always exist in the dictionary, you can force-unwrap it as data["Score"]! to make the type String (without casting, but crashing if it doesn't exist). Otherwise handle the nil in some way, e.g., if let str = data["Score"] or str = data["Score"] ?? "0".
Use like this:
if let dataDictionary = context as? [String: Any] {
if let someString = dataDictionary["score"] as? String {
print(someString)
}
}
We don't know what kind of data you're working with but you're likely either getting back a dictionary actually of type [String: String], in which case you would:
func getData(context: [String: String]) {
guard let str = context["Score"] else {
return
}
print(str)
}
...or you're really getting back a dictionary of type [String: Any] (more common with database dictionaries), in which case you would:
func getData(context: [String: Any]) {
guard let str = context["Score"] as? String else {
return
}
print(str)
}
I've got this method below which inserts a value into a dictionary. In Swift 2.x, it worked as is. When switching to Swift 3, I get the "Ambiguous reference to member 'subscript'" error.
Can anyone help me out in understanding why. Thanks
private func GetNames() -> AnyObject? {
var returnList : [NSDictionary] = []
let list = self.Names as! [NSDictionary]
for name in list {
var dict : Dictionary<NSObject, AnyObject> = [:]
dict["id"] = name["id"] // <-- ******Error*******
dict["name"] = name["name"] // <-- ******Error*******
returnList.append(dict as NSDictionary)
}
return returnList as AnyObject
}
In Swift 3 String is type of Structure, so it will not work with NSObject and you have declare dictionary thats key type is NSObject that is the reason you are getting that error. To solve the error simply change type of dictionary to [String:Any] form [NSObject:AnyObject] and you all set to go.
var dict : [String:Any] = [:]
Note: Also in Swift don't use NSDictionary and NSArray use Swift's native type dictionary and array.
The error occurs because Swift 3 needs to know all types which are subscripted by key or index.
Using Foundation collection types is counterproductive because they lack exactly that type information the compiler needs.
Returning an optional AnyObject rather than the distinct (non-optional) [[String:Any]] is counterproductive, too.
The solution is to use only (as) specific (as possible) native Swift types.
private func getNames() -> [[String:Any]] {
var returnList = [[String:Any]]()
let list = self.Names as! [[String:Any]]
for name in list {
var dict = [String:Any]()
dict["id"] = name["id"]
dict["name"] = name["name"]
returnList.append(dict)
}
return returnList
}
or swiftier if it's guaranteed that both keys name and id always exist
private func getNames() -> [[String:Any]] {
let list = Names as! [[String:Any]]
return list.map { ["id" : $0["id"]!, "name" : $0["name"]!] }
}
I'm trying to look for items equal or less than zero in my query like so.....
for zeroItems in snapshot.value as! NSDictionary where zeroItems.value["Value"] as! Int <= 0
I'm getting an Expected type error. Please explain and show me how to correct.
You may want to consider applying a filter to simplify the logic to only include elements less than zero. I've created a working example in the IBM Swift Sandbox.
import Foundation
// Construct the data structure with demo data
struct Snapshot {
let value: NSDictionary
}
let dict: NSDictionary = [ "a" : -1, "b" : 0, "c" : 1]
let snapshot = Snapshot(value: dict)
//Apply a filter
let zeroItems = snapshot.value.filter{Int($0.1 as! NSNumber) <= 0}
//View the result
print(zeroItems)
Almost certainly you've overused Any if you have to use this many as! casts in a single line. Create a custom struct for this type and convert the data to that. If you're passing around NSDictionary and using as! very much, you're fighting the system.
That said, to make this work you probably just need more parentheses. Something like:
for zeroItems in (snapshot.value as! NSDictionary) where (zeroItems.value["Value"] as! Int) <= 0
But this is horrible Swift, so avoid this if possible.
You interpreting snapshot.value as Dictionary, so zeroItems is a tuple of key and value.
As I understand you having array of dictionaries, and you want to filter them by "Value" key, right?
If so then you may use following code:
// dictionary with Value key, and Int value
let dict: [String: Any] = ["Value":-1]
// array of dictionaries
let value: [[String: Any]] = [dict, <**add here as many as you want**>]
// get only array of integeres
let onlyIntegers = value.flatMap { $0["Value"] as? Int }.filter {$0 <= 0 }
print(onlyIntegers)
// get array of dictionaries which passes <=0 check
let onlyLessOrEqualTo0 = value.filter { dict in
if let value = dict["Value"] as? Int {
return value <= 0
}
return false
}
print(onlyLessOrEqualTo0)
First of all, adding some parentheses will help in seeing where the problem is:
for zeroItems in myDict as! NSDictionary where (zeroItems.value["Value"] as! Int) <= 0
Now, we get "Type 'Any' has no subscript members". So the problem is that you haven't told Swift what the type is of zeroItems's value, which I think is a dictionary of , judging from your code:
This compiles for me:
for zeroItems in myDict as! Dictionary<String, Dictionary<String, Int>> where zeroItems.value["Value"]! <= 0
However, this is not pretty. You could probably get more readable code using filter as was suggested in another answer.
I've initialized a dictionary of type [NSObject: AnyObject] so I can save it into NSUserDefaults.
Here's what it looks like:
var allMetadata: [NSObject: AnyObject] = [
String: [String: String]
// Example: "project30": ["deliverablepath": "hello"]
]
I give deliverablepath a value from the very beginning, and later on I want to update it. I've tried this:
allMetadata[arrayOfProjectIDs[index]]!["deliverablepath"]! = "goodbye"
But I get the error
Operand of postfix '!' should have optional type; type is '(NSObject,
AnyObject)'
I know about updateValue(), but it seems to overwrite adjacent keys in the first nested layer, so it's not working for me.
Any ideas?
Use question optional to avoid "let pyramid"
var allMetadata: [String: [String: String]] = ["a": ["b": "c"]]
allMetadata["a"]?["b"] = "z" // ok!
allMetadata["q"]?["b"] = "d" // nil
UPD:
If you want to cast directly, you should try this:
var allMetadata: [NSObject: AnyObject] = ["a": ["b": "c"]]
if var dict = allMetadata["a"] as? [String: String] {
dict["b"] = "z"
// for dict update, because it's value typed
allMetadata["a"] = dict
}
Mention, that I've written "var", not "let" in condition.
To do this in a safe way, it is best to do this in an if let pyramid as follows:
if let projectId = arrayOfProjectIDs[index] {
if var project = allMetadata[projectId] as? [String:String] {
project["deliverablePath"] = "Goodbye"
}
}
That is not too bad actually.
I want to give an alternative answer here.
I understand the original question is about how to deal with nested arrays and dictionaries, but I think it is worth mentioning that this kind of data model may be better implemented with a more formal API.
For example, how about this:
class Project {
var id: String
var deliverablePath: String
... etc ...
}
class ProjectRepository {
func getProjectWithId(id: String) -> Project? {
...
}
}
Then you can use high level code like:
if let project = repository.getProjectWithId("") {
project.deliverablePath = "Goodbye"
}
Underneath you can still implement this with dictionaries and arrays of course.
I am loading a dictionary of dictionaries from a plist file like so:
var dict:NSDictionary?
if let path = NSBundle.mainBundle().pathForResource("ISA", ofType: "plist"){
dict = NSDictionary(contentsOfFile: path)
}else{
println("Could not load ISA file")
}
Given that I know the form of the dictionary, at which point should I cast its type? For example should i do:
return dict as [String : [String : Int]]
Or would it be better to return an NSDictionary and then cast the types as I use them:
dict.allKeys as [String]
EDIT:
If I do it the first way I run into trouble later when I try to access the keys:
var keys = d.keys
var length0 = keys[0].utf16Count
Produces the error:
LazyBidirectionalCollection<MapCollectionView<Dictionary<String,[String,Int]>,String>> is not convertible to [String]
Many thanks, Ben