How to create Dictionary that can hold anything in Key? or all the possible type it capable to hold

Swift

Swift Problem Overview


I want to create a Dictionary that does not limit the key type (like NSDictionary)

So I tried

var dict = Dictionary<Any, Int>()

and

var dict = Dictionary<AnyObject, Int>()

resulting

error: type 'Any' does not conform to protocol 'Hashable'
var dict = Dictionary<Any, Int>()
           ^
<REPL>:5:12: error: cannot convert the expression's type '<<error type>>' to type '$T1'
var dict = Dictionary<Any, Int>()
           ^~~~~~~~~~~~~~~~~~~~~~

OK, I will use Hashable

var dict = Dictionary<Hashable, Int>()

but

error: type 'Hashable' does not conform to protocol 'Equatable'
var dict = Dictionary<Hashable, Int>()
           ^
Swift.Equatable:2:8: note: '==' requirement refers to 'Self' type
  func ==(lhs: Self, rhs: Self) -> Bool
       ^
Swift.Hashable:1:10: note: type 'Hashable' does not conform to inherited protocol 'Equatable.Protocol'
protocol Hashable : Equatable
         ^
<REPL>:5:12: error: cannot convert the expression's type '<<error type>>' to type '$T1'
var dict = Dictionary<Hashable, Int>()
           ^~~~~~~~~~~~~~~~~~~~~~~~~~~

So Hashable inherited from Equatable but it does not conform to Equatable??? I don't understand...

Anyway, keep trying

typealias KeyType = protocol<Hashable, Equatable> // KeyType is both Hashable and Equatable
var dict = Dictionary<KeyType, Int>() // now you happy?

with no luck

error: type 'KeyType' does not conform to protocol 'Equatable'
var dict = Dictionary<KeyType, Int>()
           ^
Swift.Equatable:2:8: note: '==' requirement refers to 'Self' type
  func ==(lhs: Self, rhs: Self) -> Bool
       ^
Swift.Hashable:1:10: note: type 'KeyType' does not conform to inherited protocol 'Equatable.Protocol'
protocol Hashable : Equatable
         ^
<REPL>:6:12: error: cannot convert the expression's type '<<error type>>' to type '$T1'
var dict = Dictionary<KeyType, Int>()
           ^~~~~~~~~~~~~~~~~~~~~~~~~~

I am so lost now, how can I make compiler happy with my code?


I want to use the dictionary like

var dict = Dictionary<Any, Int>()
dict[1] = 2
dict["key"] = 3
dict[SomeEnum.SomeValue] = 4

I know I can use Dictionary<NSObject, Int>, but it is not really what I want.

Swift Solutions


Solution 1 - Swift

Swift 3 update

You can now use AnyHashable which is a type-erased hashable value, created exactly for scenarios like this:

var dict = Dictionary<AnyHashable, Int>()

Solution 2 - Swift

I believe that, as of Swift 1.2, you can use an ObjectIdentifier struct for this. It implements Hashable (and hence Equatable) as well as Comparable. You can use it to wrap any class instance. I'm guessing the implementation uses the wrapped object's underlying address for the hashValue, as well as within the == operator.

Solution 3 - Swift

I took the liberty of cross-posting / linking to this question on a separate post on the Apple Dev forums and this question is answered here.

Edit

This answer from the above link works in 6.1 and greater:

struct AnyKey: Hashable {
	private let underlying: Any
	private let hashValueFunc: () -> Int
	private let equalityFunc: (Any) -> Bool
	
	init<T: Hashable>(_ key: T) {
		underlying = key
		// Capture the key's hashability and equatability using closures.
		// The Key shares the hash of the underlying value.
		hashValueFunc = { key.hashValue }
		
		// The Key is equal to a Key of the same underlying type,
		// whose underlying value is "==" to ours.
		equalityFunc = {
			if let other = $0 as? T {
				return key == other
			}
			return false
		}
	}
	
	var hashValue: Int { return hashValueFunc() }
}

func ==(x: AnyKey, y: AnyKey) -> Bool {
	return x.equalityFunc(y.underlying)
}

Solution 4 - Swift

Dictionary is struct Dictionary<Key : Hashable, Value>... Which means that Value could be anything you want, and Key could be any type you want, but Key must conform to Hashable protocol.

You can't create Dictionary<Any, Int>() or Dictionary<AnyObject, Int>(), because Any and AnyObject can't guarantee that such a Key conforms Hashable

You can't create Dictionary<Hashable, Int>(), because Hashable is not a type it is just protocol which is describing needed type.

> So Hashable inherited from Equatable but it does not conform to > Equatable??? I don't understand...

But you are wrong in terminology. Original error is

type 'Hashable' does not conform to inherited protocol 'Equatable.Protocol' That means that Xcode assuming 'Hashable' as some type, but there is no such type. And Xcode treat it as some kind empty type, which obviously does not conform any protocol at all (in this case it does not conform to inherited protocol Equatable)

Something similar happens with KeyType.

> A type alias declaration introduces a named alias of an existing type into your program.

You see existing type. protocol<Hashable, Equatable> is not a type it is protocol so Xcode again tells you that type 'KeyType' does not conform to protocol 'Equatable'

You can use Dictionary<NSObject, Int> just, because NSObject conforms Hashable protocol.

Swift is strong typing language and you can't do some things like creating Dictionary that can hold anything in Key. Actually dictionary already supports any can hold anything in Key, which conforms Hashable. But since you should specify particular class you can't do this for native Swift classes, because there is no such master class in Swift like in Objective-C, which conforms air could conform (with a help of extensions) to Hashable

Of course you can use some wrapper like chrisco suggested. But I really can't imagine why you need it. It is great that you have strong typing in Swift so you don't need to worry about types casting as you did in Objective-C

Solution 5 - Swift

Hashable is just a protocol so you can't specify it directly as a type for the Key value. What you really need is a way of expressing "any type T, such that T implements Hashable. This is handled by type constraints in Swift:

func makeDict<T: Hashable>(arr: T[]) {
  let x = Dictionary<T, Int>()
}

This code compiles.

AFAIK, you can only use type constraints on generic functions and classes.

Solution 6 - Swift

This doesn't exactly answer the question, but has helped me.

The general answer would be implement Hashable for all your types, however that can be hard for Protocols because Hashable extends Equatable and Equatable uses Self which imposes severe limitations on what a protocol can be used for.

Instead implement Printable and then do:

var dict = [String: Int]
dict[key.description] = 3

The implementation of description has to be something like:

var description : String {
    return "<TypeName>[\(<Field1>), \(<Field2>), ...]"
}

Not a perfect answer, but the best I have so far :(

Solution 7 - Swift

This does not answer the OP's question, but is somewhat related, and may hopefully be of use for some situations. Suppose that what you really want to do is this:

   public var classTypeToClassNumber = [Any.Type : Int]()

But Swift is telling you "Type 'Any.Type' does not conform to protocol Hashable".

Most of the above answers are about using object instances as a dictionary key, not using the type of the object. (Which is fair enough, that's what the OP was asking about.) It was the answer by Howard Lovatt that led me to a usable solution.

public class ClassNumberVsClassType {

   public var classTypeToClassNumber = [String : Int]()
   
   public init() {

      classTypeToClassNumber[String(describing: ClassWithStringKey.self)] = 367622
      classTypeToClassNumber[String(describing: ClassBasedOnKeyedItemList3.self)] = 367629
      classTypeToClassNumber[String(describing: ClassBasedOnKeyedItemList2.self)] = 367626
      classTypeToClassNumber[String(describing: ClassWithGuidKey.self)] = 367623
      classTypeToClassNumber[String(describing: SimpleStruct.self)] = 367619
      classTypeToClassNumber[String(describing: TestData.self)] = 367627
      classTypeToClassNumber[String(describing: ETestEnum.self)] = 367617
      classTypeToClassNumber[String(describing: ClassBasedOnKeyedItemList0.self)] = 367624
      classTypeToClassNumber[String(describing: ClassBasedOnKeyedItemList1.self)] = 367625
      classTypeToClassNumber[String(describing: SimpleClass.self)] = 367620
      classTypeToClassNumber[String(describing: DerivedClass.self)] = 367621
   }
   
   public func findClassNumber(_ theType : Any.Type) -> Int {
      var s = String(describing: theType)
      if s.hasSuffix(".Type") {
         s = s.substring(to: s.index(s.endIndex, offsetBy: -5))  // Remove ".Type"
      }
      let classNumber = _classTypeToClassNumber[s]
      return classNumber != nil ? classNumber! : -1
   }
}

EDIT:

If the classes involved are defined in different modules, and may have conflicting class names if you neglect the module name, then substitute "String(reflecting:" for "String(describing:", both when building up the dictionary and when doing the lookup.

Solution 8 - Swift

You can use the class name as a Hashable, e.g.:

var dict = [String: Int]
dict[object_getClassName("key")] = 3

See https://stackoverflow.com/questions/24006165/how-do-i-print-the-type-or-class-of-a-variable-in-swift for how you might get the class name.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionBryan ChenView Question on Stackoverflow
Solution 1 - SwiftYarneoView Answer on Stackoverflow
Solution 2 - SwiftjulesView Answer on Stackoverflow
Solution 3 - SwiftChris ConoverView Answer on Stackoverflow
Solution 4 - SwiftSilmarilView Answer on Stackoverflow
Solution 5 - SwiftBillView Answer on Stackoverflow
Solution 6 - SwiftHoward LovattView Answer on Stackoverflow
Solution 7 - SwiftRenniePetView Answer on Stackoverflow
Solution 8 - SwiftHoward LovattView Answer on Stackoverflow