Is there an easy way to compare two [String: AnyObject] dictionaries in swift, since it doesn't accept the == operator?
By comparing two dictionaries, I mean checking that they have the same exact keys and for every key they have the same values.
As already mentioned by Hot Licks you can use NSDictionary method isEqualToDictionary() to check if they are equal as follow:
let dic1: [String: AnyObject] = ["key1": 100, "key2": 200]
let dic2: [String: AnyObject] = ["key1": 100, "key2": 200]
let dic3: [String: AnyObject] = ["key1": 100, "key2": 250]
println( NSDictionary(dictionary: dic1).isEqualToDictionary(dic2) ) // true
println( NSDictionary(dictionary: dic1).isEqualToDictionary(dic3) ) // false
you can also implement a custom operator "==" as follow:
public func ==(lhs: [String: AnyObject], rhs: [String: AnyObject] ) -> Bool {
return NSDictionary(dictionary: lhs).isEqualToDictionary(rhs)
}
println(dic1 == dic2) // true
println(dic1 == dic3) // false
Xcode 9 • Swift 4
From the docs, dictionary is now defined as a struct:
struct Dictionary<Key : Hashable, Value> : Collection, ExpressibleByDictionaryLiteral
Description
A collection whose elements are key-value pairs. A
dictionary is a type of hash table, providing fast access to the
entries it contains. Each entry in the table is identified using its
key, which is a hashable type such as a string or number. You use that
key to retrieve the corresponding value, which can be any object. In
other languages, similar data types are known as hashes or associated
arrays. Create a new dictionary by using a dictionary literal. A
dictionary literal is a comma-separated list of key-value pairs, in
which a colon separates each key from its associated value, surrounded
by square brackets. You can assign a dictionary literal to a variable
or constant or pass it to a function that expects a dictionary.
Here’s how you would create a dictionary of HTTP response codes and their related messages:
var responseMessages = [200: "OK",
403: "Access forbidden",
404: "File not found",
500: "Internal server error"]
The responseMessages variable is inferred to have type [Int: String].
The Key type of the dictionary is Int, and the Value type of the
dictionary is String.
To create a dictionary with no key-value pairs, use an empty dictionary literal ([:]).
var emptyDict: [String: String] = [:]
Any type that conforms to the Hashable protocol can be used as a dictionary’s Key type, including all of Swift’s basic types. You can use your own custom types as dictionary keys by making them conform to the Hashable protocol.
We don't need to define a custom operator anymore:
From the docs:
static func ==(lhs: [Key : Value], rhs: [Key : Value]) -> Bool
Testing:
let dic1 = ["key1": 100, "key2": 200]
let dic2 = ["key1": 100, "key2": 200]
let dic3 = ["key1": 100, "key2": 250]
print(dic1 == dic2) // true
print(dic1 == dic3) // false
In the example above all dictionary keys and values are the same type.
If we try to compare two dictionaries of type [String: Any] Xcode will complain that Binary operator == cannot be applied to two [String: Any] operands.
let dic4: [String: Any] = ["key1": 100, "key2": "200"]
let dic5: [String: Any] = ["key1": 100, "key2": "200"]
let dic6: [String: Any] = ["key1": 100, "key2": Date()]
print(dic4 == dic5) // Binary operator == cannot be applied to two `[String: Any]` operands
But we can extend the == operator functionality implementing an infix operator, casting Swift Dictionary to NSDictionary and constraining the dictionary values to Hashable Protocol:
Xcode 11 • Swift 5.1
public func ==<K, L: Hashable, R: Hashable>(lhs: [K: L], rhs: [K: R] ) -> Bool {
(lhs as NSDictionary).isEqual(to: rhs)
}
Testing:
let dic4: [String: AnyHashable] = ["key1": 100, "key2": "200"]
let dic5: [String: AnyHashable] = ["key1": 100, "key2": "200"]
let dic6: [String: AnyHashable] = ["key1": 100, "key2": Date()]
print(dic4 == dic5) // true
print(dic4 == dic6) // false
let dic7: [String: String] = [ "key2": "200"]
let dic8: [String: Date] = [ "key2": Date()]
print(dic7 == dic8) // false
Swift 4 Update:
Comparing Dictionaries is now native! (Docs here)
Swift 3:
Leo Dabus already has an excellently written post with the accepted solution. However, for me, I found that it needed one more step to be fully usable. As you can see from his code, you need to set your dictionary type to [AnyHashable: Any], or otherwise you'll get Binary operator '==' cannot be applied to two '[String : Any]' operands, to use a dictionary common in deserializing JSON for my example.
Generics to the rescue!:
// Swift 3.0
func == <K, V>(left: [K:V], right: [K:V]) -> Bool {
return NSDictionary(dictionary: left).isEqual(to: right)
}
or in another case I had, with [String: Any?]:
func == <K, V>(left: [K:V?], right: [K:V?]) -> Bool {
guard let left = left as? [K: V], let right = right as? [K: V] else { return false }
return NSDictionary(dictionary: left).isEqual(to: right)
}
In Swift 2, when both Key and Value are Equatable, you can use == on the dictionary itself:
public func ==<Key : Equatable, Value : Equatable>(lhs: [Key : Value], rhs: [Key : Value]) -> Bool
And, NSObject is Equatable:
public func ==(lhs: NSObject, rhs: NSObject) -> Bool
In your case, if you are working with Obj-C objects that you want to compare using isEqual:, you can simply use NSObject as your value type (rather than AnyObject).
Without custom type in value of Dictionary, in Swift 2+ you can use the == operator to compare two Dictionary to check if they are equal or not.
But in some cases with custom types as the Dictionary's value (like struct), you must adopt Equatable in order for that custom type to use == operator.
Ex:
// custom type
struct Custom: Equatable {
var value: Int
}
// MARK: adopting Equatable
func ==(lhs: Custom, rhs: Custom) -> Bool {
if lhs.value == rhs.value {
return true
} else {
return false
}
}
Now you can use the == operator to compare two dictionaries:
let dic3: [String: Custom] = ["key1": Custom(value:1), "key2": Custom(value:2)]
let dic4: [String: Custom] = ["key1": Custom(value:1), "key2": Custom(value:2)]
if (dic3 == dic4) {
print("equal")
} else {
print("not equal")
}
Related
I have a set of functions that extract values from a dictionary, where the dictionary's value type is Any (it comes from JSON data, and I don't have control over the value types):
func get<T>(_ dict: [String: Any], key: String) throws -> T {
// cast the value for the key to a T, and return
}
func get<T: RawRepresentable>(_ dict: [String: Any], key: String) throws -> T {
// cast the value for the key to a T.RawValue, init a T, and return
}
This works fine. The first function can cast a value of a scalar type and return it, while the second can convert a raw value for an enum type into the higher-level Swift type.
If I add a third variant, the compiler becomes unhappy and I get a lot of "ambiguous use of" errors, flagging all three functions as candidates:
func get<T: RawRepresentable>(_ dict: [String: Any], key: String) throws -> T where T.RawValue == Double {
// try to extract the value as an Double, init a T, and return
// if that fails, extract as an Int, cast to a Double, init a T, and return
}
The idea with this function is that a double value might appear without its decimal, and actually be an Int in the dictionary, but it needs to be a Double in strongly-typed Swift-land.
Why is the compiler able to resolve the ambiguity without the third variant, but considers all three as equally valid candidates when it exists?
Edit: here is a playground that illustrates the problem.
enum E: Error {
case error
}
func get<T>(_ dict: [String: Any], key: String) throws -> T {
guard let value = dict[key] as? T else { throw E.error }
return value
}
func get<T: RawRepresentable>(_ dict: [String: Any], key: String) throws -> T {
guard let rawValue = dict[key] as? T.RawValue else { throw E.error }
return T(rawValue: rawValue)!
}
func get<T: RawRepresentable>(_ dict: [String: Any], key: String) throws -> T where T.RawValue == Double {
if let doubleValue = dict[key] as? Double {
return T(rawValue: doubleValue)!
} else if let intValue = dict[key] as? Int {
return T(rawValue: Double(intValue))!
}
throw E.error
}
struct Wrapped: RawRepresentable {
typealias RawValue = Double
var rawValue: Double
init?(rawValue: Double) {
self.rawValue = rawValue
}
}
let dict: [String: Any] = ["int": 1, "double": 1.1]
let int: Int = try! get(dict, key: "int")
let double: Double = try! get(dict, key: "double")
let wrapped: Wrapped = try! get(dict, key: "int")
This produces a compile error (ambiguous use of get(_:key:)). Commenting out the third specialization of get<T> resolves the compiler error, but results in a runtime error because the integer value of the key int can't be cast using as? into a Double. Changing the key to double resolves that, but doesn't address the problem I was trying to solve.
I should note that this is now an academic question for me, since I've changed my parsing approach to use Codable and it handles parsing both floating point and integer values into Float/Double types in Swift.
Is there a way to add multiple, optional constraints to a generic? I'd like my generic to be either a string or a bool. Something like:
func myFunction<T>(_ dict: [String: T]) where T == String || T == Bool {
// Do stuff with dict
}
You can just create a protocol and make String and Bool conform to it:
protocol StringOrBool { }
extension Bool: StringOrBool {}
extension String: StringOrBool {}
func myFunction<T: StringOrBool>(_ dict: [String: T]) {
print(dict)
}
myFunction(["a": true])
myFunction(["b": "test"])
myFunction(["b": 1]) // error: MyPlayground Xcode 11.playground:706:1: error: global function 'myFunction' requires that 'Int' conform to 'StringOrBool'
You can't event constrain a generic to one single type:
// error: Same-type requirement makes generic parameter 'T' non-generic
func myFunction<T>(_ dict: [String: T]) where T == String {
}
You probably want some overload of your function for your types:
func myFunction(_ dict: [String: String]) {
print("String")
}
func myFunction(_ dict: [String: Bool]) {
print("Bool")
}
let strings = ["a": "b"]
let bools = ["a": false]
myFunction(strings) // print String
myFunction(bools) // print Bool
I have a struct with several properties. How can I write a function that takes a dictionary of type [String: Any] and creates another dictionary of type [String: Any] that contains only keys and values where the input dictionary's value was different from the struct property value with the same name?
A struct:
struct MyStruct {
var a: Bool = false
var b: String = "random"
var c: Int = 2
}
Desired function call:
let myStruct = MyStruct()
let input: [String: Any] = ["b": "test", "c": 2]
let result: [String: Any] = myStruct.getDiff(input)
Desired result for the example input:
result = ["b": "test"]
Besides a struct, how would this be done for comparing the [String: Any] to a class?
The specific syntax you've provided is probably not possible. Packing things into a [String: Any] likely loses too much information to be recovered. But your overall goal is certainly possible.
The important piece is input. Rather than [String: Any], we're going to use an explicit type, ValueChange:
let input = [
ValueChange(key: "b", changedTo: "test"),
ValueChange(key: "c", changedTo: 2),
]
Creating a new type like this allows us to capture all the types, and enforce certain rules, particularly that the values are Equatable:
struct ValueChange {
init<Value: Equatable>(key: String, changedTo newValue: Value) {...}
}
I'll come back to ValueChange in a moment, but first I want to go back to how it'll be used. Since you want a .getDiff(...) syntax, it'll be best to create an extension using a protocol:
protocol DictionaryDiffComputing {}
extension DictionaryDiffComputing {
func getDiff(_ changes: [ValueChange]) -> [String: Any] {
Dictionary(uniqueKeysWithValues:
changes.compactMap { $0.changedValue(self) })
}
}
The protocol has no requirements. It just exists to say "these types have the getDiff method." This method needs ValueChange to provide us a (String, Any) tuple if the value has changed.
This is where the problem gets interesting, I'll just show the answer and then discuss it.
struct ValueChange {
let changedValue: (_ object: Any) -> (String, Any)? // (key, newValue)
init<Value: Equatable>(key: String, changedTo newValue: Value) {
self.changedValue = { object in
// Get the old value as an Any using Mirror
guard let oldAnyValue: Any = Mirror(reflecting: object)
.children
.first(where: { $0.label == key })?
.value
else {
assertionFailure("Unknown key: \(key)")
return nil
}
// Make sure it's the correct type
guard let oldValue = oldAnyValue as? Value else {
assertionFailure("Bad type for values (\(oldAnyValue)). Expected: \(Value.self)")
return nil
}
// Compare the values
return newValue != oldValue ? (key, newValue) : nil
}
}
}
This uses Mirror to pull out the old value to compare as an Any type, then it converts it to the correct Value type. This is the power of the generic init. Since we know the type a compile time, we can capture it inside this closure, erasing that type from the outside world, but being able to work with it at runtime.
extension MyStruct: DictionaryDiffComputing {}
let myStruct = MyStruct()
myStruct.getDiff(input) // ["b": "test"]
What I really don't like about this answer is that it's very unsafe. Note the two calls to assertionFailure. There is nothing about ValueChange that ensures that the key exists or that the value is the correct type. If you change the name or type a property, your program will either crash or behave incorrectly, and there's nothing the compiler can do to help you.
You can make this a lot more type-safe and the code much simpler at the cost of a slightly more verbose calling syntax:
protocol DictionaryDiffComputing {}
struct ValueChange<Root> {
let changedValue: (_ object: Root) -> (String, Any)? // (key, newValue)
init<Value: Equatable>(key: String, keyPath: KeyPath<Root, Value>, changedTo newValue: Value) {
self.changedValue = { newValue != $0[keyPath: keyPath] ? (key, newValue) : nil }
}
}
extension DictionaryDiffComputing {
func getDiff(_ changes: [ValueChange<Self>]) -> [String: Any] {
Dictionary(uniqueKeysWithValues:
changes.compactMap { $0.changedValue(self) })
}
}
let myStruct = MyStruct()
let input: [ValueChange<MyStruct>] = [
ValueChange(key: "b", keyPath: \.b, changedTo: "test"),
ValueChange(key: "c", keyPath: \.c, changedTo: 2),
]
myStruct.getDiff(input)
If you use this approach, you know that the property exists on this type, and that the value is the correct type for that property. You also get some extra power, since you can use any key path you like starting at this root type. That means you can do things like:
ValueChange(key: "b_length", keyPath: \.b.count, changedTo: 4),
You could cleanup the requirement for key in ValueChange by adding some mapping dictionary of key path to key name (a static var protocol requirement for example), but I don't know of a way to generate this automatically, and I don't know any good way to convert a key path into an appropriate string.
Is there any way to check if two [String: Any] are identical ?
let actual: [[String: Any]] = [
["id": 12345, "name": "Rahul Katariya"],
["id": 12346, "name": "Aar Kay"]
]
var expected: [[String: Any]]!
if actual == expected {
print("Equal")
}
Basically i want Dictionary to conform to Equatable protocol in Swift 3.
For Xcode 7.3, swift 2.2
A dictionary is of type : [String:AnyObject] or simply put NSDictionary
let actual: [String: AnyObject] = ["id": 12345, "name": "Rahul Katariya"]
var expected: [String: AnyObject] = ["id": 12346, "name": "Aar Kay"]
print(NSDictionary(dictionary: actual).isEqualToDictionary(expected))//False
For Xcode 8.beta 6, Swift 3
Dictionary is defined as:
struct Dictionary<Key : Hashable, Value> : Collection, ExpressibleByDictionaryLiteral
NSDictionary has the following convenience initializer:
convenience init(dictionary otherDictionary: [AnyHashable : Any])
So you can use AnyHashable type for Key and Any type for Value
let actual: [String: Any] = ["id": 12345, "name": "Rahul Katariya"]
var expected: [String: Any] = ["id": 12346, "name": "Aar Kay"]
print(NSDictionary(dictionary: actual).isEqual(to: expected))//False
Conformance to Equatable aside; for the exercise you could write your own isEqual function to compare two [T: Any] dictionaries for a subset of (Equatable) types that you know the value wrapped by Any is limited to. By attempted conversion to these types (e.g. in a switch statement, as below), you could compare the dictionary's values (for each given key) one by one after their conversion to these given types. E.g.
// Usable if the 'Any' values in your dict only wraps
// a few different types _that are known to you_.
// Return false also in case value cannot be successfully
// converted to some known type. This might yield a false negative.
extension Dictionary where Value: Any {
func isEqual(to otherDict: [Key: Any],
allPossibleValueTypesAreKnown: Bool = false) -> Bool {
guard allPossibleValueTypesAreKnown &&
self.count == otherDict.count else { return false }
for (k1,v1) in self {
guard let v2 = otherDict[k1] else { return false }
switch (v1, v2) {
case (let v1 as Double, let v2 as Double) : if !(v1.isEqual(to: v2)) { return false }
case (let v1 as Int, let v2 as Int) : if !(v1==v2) { return false }
case (let v1 as String, let v2 as String): if !(v1==v2) { return false }
// ... fill in with types that are known to you to be
// wrapped by the 'Any' in the dictionaries
default: return false
}
}
return true
}
}
Usage:
/* example setup */
var dict1: [String: Any] = ["id": 12345, "name": "Rahul Katariya", "weight": 70.7]
var dict2: [String: Any] = ["id": 12346, "name": "Aar Kay", "weight": 83.1]
/* example usage */
print(dict1.isEqual(to: dict2, allPossibleValueTypesAreKnown: true))
// false
dict2["name"] = "Rahul Katariya"
dict2["weight"] = 70.7
print(dict1.isEqual(to: dict2, allPossibleValueTypesAreKnown: true))
// false
dict2["id"] = 12345
print(dict1.isEqual(to: dict2, allPossibleValueTypesAreKnown: true))
// true
class Foo {}
dict1["id"] = Foo()
dict2["id"] = Foo()
print(dict1.isEqual(to: dict2, allPossibleValueTypesAreKnown: true))
// false! (we haven't implemented this attempted conversion!)
// incompatable keys cause error as expected an intended
let dict3: [Int: Any] = [1:2]
dict1.isEqual(to: dict3)
/* error: cannot convert value of type '[Int : Any]'
to expected argument type '[String : Any]' */
Just note the danger that the as conversion may yield a false positive (true) as it can allow mapping from two different types to a common other type, e.g. slicing away derived class differences when casting two derived class instances to their common parent type:
class Base: Equatable {}
func ==(lhs: Base, rhs: Base) -> Bool { return true }
class DerivedA : Base {
let foo = "foo"
}
class DerivedB : Base {
let bar = 4.2
}
let a = DerivedA()
let b = DerivedB()
switch (a, b) {
case (let a as Base, let b as Base): print(a == b)
default: ()
} // sliced by conversion! prints "true"
If you'd rather like a failed "known types conversion" to return nil (whereas successful conversions will always yield true/false, based on subsequent equality testing), you could extend the above to (the even messier)
// a 'nil' return here would correspond to an invalid call
extension Dictionary where Value: Any {
func isEqual(to otherDict: [Key: Any],
allPossibleValueTypesAreKnown: Bool = false) -> Bool? {
guard allPossibleValueTypesAreKnown else { return nil }
guard self.count == otherDict.count else { return false }
for (k1,v1) in self {
guard let v2 = otherDict[k1] else { return false }
switch (v1, v2) {
case (let v1 as Double, let v2 as Double) : if !(v1.isEqual(to: v2)) { return false }
case (let v1 as Int, let v2 as Int) : if !(v1==v2) { return false }
case (let v1 as String, let v2 as String): if !(v1==v2) { return false }
// ...
case (_ as Double, let v2): if !(v2 is Double) { return false }
case (_, _ as Double): return false
case (_ as Int, let v2): if !(v2 is Int) { return false }
case (_, _ as Int): return false
case (_ as String, let v2): if !(v2 is String) { return false }
case (_, _ as String): return false
default: return nil
}
}
return true
}
}
/* Example as per above will yield (printout):
Optional(false)
Optional(false)
Optional(true)
nil */
Note however that the value by value equality testing above is short-circuited in case of a false hit, which mean that depending on the random order of the non-ordered dictionaries (non-ordered collection), a special case may return nil as well as false, given two non-equal dictionaries. This special case occurs for two dictionary of non-equal values (non-equality for a known type value-value pair) which also hold an value type not included in the attempted casting: if the non-equality of known types is hit first, false will be returned, whereas if a failed conversion is hit first, nil will be returned. Either way, a nil return means the call should be considered invalid, as caller stated that allPossibleValueTypesAreKnown was true (which a failed conversion implies is false).
The type Any is not Equatable in Swift, so any collection types including Any cannot be Equatable.
You can write something like this in Swift 3/Xcode 8 beta 6:
if actual as NSArray == expected as NSArray {
print("Equal")
}
But, as importing id as Any is just introduced in beta 6, so this behaviour may change in the near future.
With Swift 5.5 you can easily cast it to NSDictionary, as it always succeeds:
XCTAssertEqual(actual as NSDictionary, expected as NSDictionary)
I have have a dictionary. I would like to go through it and convert the values to a different type. .map{ } would be perfect except this is a dictionary and not an array. So, I found a mapPairs function on stack overflow that should work for dictionaries. Unfortunately I get a conversion error.
extension Dictionary {
// Since Dictionary conforms to CollectionType, and its Element typealias is a (key, value) tuple, that means you ought to be able to do something like this:
//
// result = dict.map { (key, value) in (key, value.uppercaseString) }
//
// However, that won't actually assign to a Dictionary-typed variable. THE MAP METHOD IS DEFINED TO ALWAYS RETURN AN ARRAY (THE [T]), even for other types like dictionaries. If you write a constructor that'll turn an array of two-tuples into a Dictionary and all will be right with the world:
// Now you can do this:
// result = Dictionary(dict.map { (key, value) in (key, value.uppercaseString) })
//
init(_ pairs: [Element]) {
self.init()
for (k, v) in pairs {
self[k] = v
}
}
// You may even want to write a Dictionary-specific version of map just to avoid explicitly calling the constructor. Here I've also included an implementation of filter:
// let testarr = ["foo" : 1, "bar" : 2]
// let result = testarr.mapPairs { (key, value) in (key, value * 2) }
// result["bar"]
func mapPairs<OutKey: Hashable, OutValue>(#noescape transform: Element throws -> (OutKey, OutValue)) rethrows -> [OutKey: OutValue] {
return Dictionary<OutKey, OutValue>(try map(transform))
}
}
var dict1 = ["a" : 1, "b": 2, "c": 3]
let convertedDict: [String: String] = dict1.mapPairs { // ERROR: cannot convert value of type '_ -> (String, Int)' to expected argument type '(String, Int) -> (String, String)'
element -> (String, Int) in
element[0] = String(element.1)
return element
}
In Swift 5 and later:
let originalDict: [TypeA: TypeB] = /* */
let convertedDict: [TypeA: TypeC] = originalDict.mapValues { /* conversion here */ }
Example:
let integerDict: [String: Int] = ["a": 1, "b": 2]
let doubleDict: [String: Double] = integerDict.mapValues(Double.init)
print(doubleDict) // ["a": 1.0, "b": 2.0]
If you want to change a dict of [String: Int] to [String: String], you can pretty much do the same as my previous answer:
let dict1 = ["a" : 1, "b": 2, "c": 3]
var dict2 = [String: String]()
dict1.forEach { dict2[$0.0] = String($0.1) }
print("\(dict2.dynamicType): \(dict2)")
Output:
Dictionary<String, String>: ["b": "2", "a": "1", "c": "3"]
I don't know if this might help, but since Swift 4.2 there is a new operator called mapValues(_:) (https://developer.apple.com/documentation/swift/dictionary/2995348-mapvalues), which would transform the result you are looking for to:
let convertedDict = dict1.mapValues { String($0) }
As the example given by the method block, you should use mapPairs like this:
let convertedDict: [String: String] = dict1.mapPairs { (key, value) in
(key, String(value))
}
Note, since Swift supports implicit inference, you don't need explicitly return.