58
votes

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?

7
why would someone download this questions? what is wrong with it ?lionserdar
I would imagine because of the vague title.George Stocker

7 Answers

98
votes

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.

19
votes

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.

4
votes

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
2
votes

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 .

1
votes

Use: Dictionary<String, AnyObject>

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

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)
-2
votes
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
]