How do I put different types in a dictionary in the Swift Language? - swift

Swift only allows a dictionary to contain a single type.
Here's the definition that is taken from the Swift book:
A dictionary is a container that stores multiple values of the same type
[...]
They differ from Objective-C’s NSDictionary and NSMutableDictionary classes, which can use any kind of object as their keys and values and do not provide any information about the nature of these objects.
If that’s the case then how are we going to create nested dictionaries?
Imagine we have a plist that holds String, Array and Dictionary items in it . If I’m allowed to hold only the same of type of items (either string, array etc.) then how am I going to use different types of items stored in the plist?
How do I put different types in the same dictionary in Swift?

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<String, Any>()
response["user"] = ["Login": "Power Ranger", "Password": "Mighty Morfin'"]
response["status"] = 200
EDIT:
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.

As has been suggested, you can use the Any type to represent a plist dictionary's values. But then how do you work with the data? Cast every value any time you look it up from the dictionary? That's really messy. A better, more type-safe way to model a plist would be to take advantage of Swift's enums, also known as algebraic data types or discriminated unions. They let you specify exactly what types are permitted in the dictionary and avoid ever having to cast. Here's an implementation, explained:
// An atomic (i.e. non-collection) data type in a plist.
enum PListNode {
case PLN_String(String)
case PLN_Integer(Int)
case PLN_Float(Double)
case PLN_Bool(Bool)
case PLN_Date(CFDate)
case PLN_Data(CFData)
}
At the most atomic level, only the above data types may be stored in a plist. Each 'node' in the plist can ultimately can only be one of these types. So we create an enum which lets us specify this.
// A value that can be stored in a plist Dictionary's key-value pair.
enum PListValue {
case PLV_Node(PListNode)
case PLV_Array(PListNode[])
case PLV_Dictionary(Dictionary<String, Box<PListValue>>)
}
typealias PList = Dictionary<String, Box<PListValue>>
A plist is basically a dictionary of key-value pairs, and each value can be either an atomic (i.e. non-collection) value; or it can be an array of atomic values; or it can be a dictionary of string-plist value pairs. The above enum expresses these constraints, and the typealias gives the plist type an easy-to-remember name.
Given the above types, we can completely express any given plist in a type-safe way, e.g.:
// Example translated from
// https://developer.apple.com/library/Mac/documentation/Darwin/Reference/ManPages/man5/plist.5.html
let myPlist: PList = [
"Year Of Birth": Box(PLV_Node(PLN_Integer(1965)))
, "Pets Names": Box(PLV_Array([]))
, "Picture": Box(PLV_Node(PLN_Data(...)))
, "City of Birth": Box(PLV_Node(PLN_String("Springfield")))
, "Name": Box(PLV_Node(PLN_String("John Doe")))
, "Kids Names": Box(
PLV_Array([PLN_String("John"), PLN_String("Kyra")])
)
]
What it means to be type-safe here is that you can process any given plist using a switch statement and cover all possibilities without the need for any casting. You're eliminating a whole class of potential runtime errors. E.g.:
// See https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Enumerations.html#//apple_ref/doc/uid/TP40014097-CH12-XID_189 for explanation
switch myPlist["Year Of Birth"] {
case Box(.PLV_Node(let plvNodeValue)):
...
case Box(.PLV_Array(let plvArrayValue)):
...
case Box(.PLV_Dictionary(let plvDictionaryValue)):
...
}
Note that it's necessary to wrap up recursive data structures in a 'box' (a pointer to the actual value) to keep their sizes finite.

NSObject works for my case while "Any" does not
var d:Dictionary<String,NSObject> = [:]
d["key1"] = "ddd"
d["key2"] = 111 //OK
NSLog("%#", d) //OK
var d2:Dictionary = Dictionary<String,Any>()
d2["key1"] = "ddd"
d2["key2"] = 111
NSLog("%#", d2) //I got error here

Use NSMutableDictionary like this :
var dictInfo : NSMutableDictionary = [ "lang_key": "1"]
dictInfo["food_type"] = lbl_TypeOfFood.text
dictInfo["search_text"] = txt_Search.text
dictInfo["date"] = lbl_Date.text
dictInfo["opening_hours"] = lbl_OpeningHours.text
hope this will work fine .

Use: Dictionary<String, AnyObject>
var dict: Dictionary<String, AnyObject> = [
"number": 1,
"string": "Hello",
]

NSMutableDictionary to Dictionary works like a charm and will allow you to put different types in a Dictionary in the Swift Language:
let nsMutableDictionary = NSMutableDictionary()
nsMutableDictionary[NSFontAttributeName] = UIFont(name: "HelveticaNeue", size: 12.0)!
nsMutableDictionary[NSForegroundColorAttributeName] = UIColor.redColor()
let dictionary: Dictionary<NSObject, AnyObject> = nsMutableDictionary
self.attributedPlaceholder = NSAttributedString(string: textParam, attributes: dictionary)

let dictionary : Dictionary = [
"key": "value",
"key2": 2,
"key3": NSString(),
2: "test",
]
One can specify types which restricts the dictionary
let dictionary : Dictionary<String, String> = [
"key": "value",
"key2": 2, // This errors
]

Related

How can I initialize an OrderedDictionary from a regular Dictionary in Swift?

Apples swift-collections package provides an alternative to a Dictionary type which guarantees to keep (not bring) its key-value pairs in order (unlike the regular Dictionary type). But how can I turn an instance of type Dictionary into an instance of type OrderedDictionary for later re-ordering?
For example, how would it work for this dict:
let regularDict: [String: String] = ["key1": "value1", "key2": "value2"]
// => EITHER {key2=>value2, key1=>value1} OR {key1=>value1, key2=>value2}
let orderedDict: OrderedDictionary<String, String> = .init(/* ??? */)
I am aware that orderedDict will have a random order of key-value entries at first, that's fine for me, I just need an OrderedDictionary instance so any future changes to the order I make don't get lost. I just want to know how to initialize it from a Dictionary in a way that is performant and makes sure the keys stay connected to the correct values.
You can use init(uniqueKeys:values:) with the regular dictionaries' keys and values:
let regularDict = ["key1": "value1", "key2": "value2"]
// => EITHER {key2=>value2, key1=>value1} OR {key1=>value1, key2=>value2}
var orderedDict = OrderedDictionary<String, String>(
uniqueKeys: regularDict.keys,
values: regularDict.values
)
// => EITHER {key2=>value2, key1=>value1} OR {key1=>value1, key2=>value2}
This will not magically sort anything though, the resulting orderedDict will have key-value entries of same random order as the regularDict. But any future changes to the order will be persistent. For example, you could order by keys of a type conforming to Comparable like this:
orderedEntries.sort(by: { $0.key < $1.key })
// => {key1=>value1, key2=>value2}

How to add/Update key/values in dict in swift?

func exercise() {
var stockTickers: [String: String] = [
"APPL" : "Apple Inc",
"HOG": "Harley-Davidson Inc",
"BOOM": "Dynamic Materials",
"HEINY": "Heineken",
"BEN": "Franklin Resources Inc"
]
stockTickers["WORK"] = ["Slack Technologies Inc"]
stockTickers["BOOM"] = ["DMC Global Inc"]
print(stockTickers["WORK"]!)
print(stockTickers["BOOM"]!)
}
Error: Cannot assign value of type '[String]' to subscript of type 'String'
I do not understand the error, I'm new to swift, can someone guide me through this and tell mw why I see this error.
Alexander explained what you need to do in abstract terms. (Voted)
Specifically, change
stockTickers["WORK"] = ["Slack Technologies Inc"]
stockTickers["BOOM"] = ["DMC Global Inc"]
To
stockTickers["WORK"] = "Slack Technologies Inc"
stockTickers["BOOM"] = "DMC Global Inc"
The expression ["Slack Technologies Inc"] defines a String array containing a single string. That's not what you want. you defined a dictionary of type [String:String]
If you wanted your dictionary to have String keys and values that contained arrays of strings, you'd have to change the way you declared your dictionary:
var stockTickers: [String: [String]] = [
"APPL" : ["Apple Inc"],
"HOG": ["Harley-Davidson Inc"],
"BOOM": ["Dynamic Materials"],
"HEINY": ["Heineken"],
"BEN": ["Franklin Resources Inc"]
]
["DMC Global Inc"] is an array literal containing a string. It evaluates to a value of type [String] (a.k.a. Array<String>).
Thus the error makes sense: you’re trying to a assign an array of strings in a place where only a String is expected.
Just remove the square brackets.

Swift: Dictionary of Dicitionaries, cannot get subscript

I've looked at other subscript issues here and I don't think they match my problem. I have a dictionary of dictionaries - Dictionary[String:Dictionary[String:String]]
In an extension I want to loop through all the values (Dictionary[String:String] and retrieve one of the values.
So I wrote this:
for dictNEO in Array(self.values) {
print(dictNEO)
print(type(of: dictNEO))
print(dictNEO["approachDate"])
}
and am getting this error on the last print line: Value of type 'Value' has no subscripts
Here's the first two print lines:
["nominalDist": "\"13.58 ", "approachDate": "\"2020-Feb-01 08:18 ± < 00:01\"", "minimumDist": "\"13.58 ", "diameter": "\"92 m - 210 m\"", "name": "\"(2017 AE5)\""]
Dictionary<String, String>
So I am confused as to why it is telling me it has no subscripts when it sees the type of as a Dictionary.
You have written this as an extension to Dictionary if I understand you correctly and that means that self is generic and defined as Dictionary<Key, Value> and not to you specific type so in your for loop you are looping over an array of [Value].
So you need to typecast Value before accessing it as a dictionary
if let dictionary = dictNEO as? [String: String] {
print(dictNEO["approachDate"])
}
but since it makes little sense to have an extension to Dictionary where you access a specific key it would be better to write it as a function. Since the dictionary is well defined now there is no issue with the last print
func printValuesForSubKey(_ key: String, _ dict: [String: [String: String]]) {
for (dictNEO) in dict.values {
print(dictNEO)
print(type(of: dictNEO))
print(dictNEO[key])
}
}
Note, I don't have an explanation why type(of:) recognises it as [String: String]
The code snippet doesn't work because values property is a collection of collections and with Array(values) you create a collection of collection of collections. In short, instead going down the code goes up and creates new collection level.
Solution with a Higher order function map:
self.values.map { print(type(of: $0)); $0["approachDate"] }
Solution with For-In Loop
for dictNEO in self.values {
print(dictNEO)
print(type(of: dictNEO))
print(dictNEO["approachDate"])
}

What is the return type of JSONSerialization.jsonObject(with:options:) in Swift?

I made the json parser in swift language.
But, many of people are using like below.
let jsonParsed = JSONSerialization.jsonObject(with: data, options: [])
guard let jsonDict = jsonParsed as? Dictionary<String, AnyObject> else { return }
...
Then, I wonder the type of jsonParsed. The JSONSerialization.jsonObject(with:options:) function reference describes that the result type is just Any.
I know the type is Dictionary because JSON. The KEY is String type but, VALUE is AnyObject? How about Any?
I know the difference between AnyObject and Any. Any also includes value type, function type.
Number is also value type in swift: Int, Float, Double...
Is that impossible return type is value type?
Json can be array also. This is valid josn
[
"1",
"2",
"3"
]
You can use this site to verify [https://jsoneditoronline.org/][1].
When you give fragmentsallowed option in the function json result can be Int,Float or any other primitive data type.
So it is not possible to return type in compile time. At run time it is possible. But return type is decided at run time.

Swift nested dictionary with nil

How can I have nil in the middle of nest Swift dictionaries?
var d: [String: AnyObject?] = [
"name": "Rodrigo",
"number": 1,
"nil": nil,
"more": [
"a": 1,
"b": 2,
"nil": nil
]
]
The second nil (inside "more") gives me the error Type of expression is ambiguous without more context.
The problem you're having here is that Swift's Dictionary is a value type, and therefore doesn't automatically conform to AnyObject. The compiler tries to help you out here, and bridge over to NSDictionary, which is an AnyObject, but unfortunately NSDictionary can't hold nil values, so you're out of luck.
As I see it, you have a couple options:
Go the standard Objective-C route and use NSNull() instead of nil in your nested array.
Rethink the structure of your data. Maybe instead of nested dictionaries, with string keys, you can build a struct PersonData that holds all the information you need in typed properties (including optionals). You may find that works even better than this approach.