Repository: segmentio/analytics-swift (swift)

Issue:
track(event:properties:) takes [String:Any], but screen(screenTitle:properties:) takes [Any:Hashable]
**Describe the bug**
There's a bit of mismatch/asymmetry between the various `track` and `screen` methods. Namely, `track(event:properties:)` takes `[String:Any]`, but `screen(screenTitle:properties:)` takes [Any:Hashable]`.

**Expected behavior**
I'd expect these to take the same thing, unless there's some reason they can't.

**Platform (please complete the following information):**
 - Library Version in use: 1.0.2
 - Platform being tested: [e.g. iOS]

Pull request:
- Renamed mapKeys to mapTransform
- Changed event functions to use [String: Any] across the board.  Fixes #51

Patch:
diff --git a/Sources/Segment/Events.swift b/Sources/Segment/Events.swift
index dc9afb36..b28bbdf5 100644
--- a/Sources/Segment/Events.swift
+++ b/Sources/Segment/Events.swift
@@ -155,7 +155,7 @@ extension Analytics {
     ///     but want to record traits, you should pass nil. For more information on how we
     ///     generate the UUID and Apple's policies on IDs, see https://segment.io/libraries/ios#ids
     ///   - traits: A dictionary of traits you know about the user. Things like: email, name, plan, etc.
-    public func identify(userId: String, traits: [String: AnyHashable]? = nil) {
+    public func identify(userId: String, traits: [String: Any]? = nil) {
         do {
             if let traits = traits {
                 let traits = try JSON(traits as Any)
@@ -178,7 +178,7 @@ extension Analytics {
     ///   - screenTitle: The title of the screen being tracked.
     ///   - category: A category to the type of screen if it applies.
     ///   - properties: Any extra metadata associated with the screen. e.g. method of access, size, etc.
-    public func screen(screenTitle: String, category: String? = nil, properties: [String: AnyHashable]? = nil) {
+    public func screen(screenTitle: String, category: String? = nil, properties: [String: Any]? = nil) {
         var event = ScreenEvent(screenTitle: screenTitle, category: category, properties: nil)
         if let properties = properties {
             do {
@@ -195,7 +195,7 @@ extension Analytics {
     /// - Parameters:
     ///   - groupId: A unique identifier for the group identification in your system.
     ///   - traits: Traits of the group you may be interested in such as email, phone or name.
-    public func group(groupId: String, traits: [String: AnyHashable]?) {
+    public func group(groupId: String, traits: [String: Any]?) {
         var event = GroupEvent(groupId: groupId)
         if let traits = traits {
             do {
diff --git a/Sources/Segment/ObjC/ObjCAnalytics.swift b/Sources/Segment/ObjC/ObjCAnalytics.swift
index 718650ef..ff9ba44f 100644
--- a/Sources/Segment/ObjC/ObjCAnalytics.swift
+++ b/Sources/Segment/ObjC/ObjCAnalytics.swift
@@ -53,7 +53,7 @@ extension ObjCAnalytics {
     ///     generate the UUID and Apple's policies on IDs, see https://segment.io/libraries/ios#ids
     ///   - traits: A dictionary of traits you know about the user. Things like: email, name, plan, etc.
     @objc(identify:traits:)
-    public func identify(userId: String, traits: [String: AnyHashable]?) {
+    public func identify(userId: String, traits: [String: Any]?) {
         analytics.identify(userId: userId, traits: traits)
     }
     
@@ -79,7 +79,7 @@ extension ObjCAnalytics {
     ///   - category: A category to the type of screen if it applies.
     ///   - properties: Any extra metadata associated with the screen. e.g. method of access, size, etc.
     @objc(screen:category:properties:)
-    public func screen(screenTitle: String, category: String?, properties: [String: AnyHashable]?) {
+    public func screen(screenTitle: String, category: String?, properties: [String: Any]?) {
         analytics.screen(screenTitle: screenTitle, category: category, properties: properties)
     }
 
@@ -96,7 +96,7 @@ extension ObjCAnalytics {
     ///   - groupId: A unique identifier for the group identification in your system.
     ///   - traits: Traits of the group you may be interested in such as email, phone or name.
     @objc(group:traits:)
-    public func group(groupId: String, traits: [String: AnyHashable]?) {
+    public func group(groupId: String, traits: [String: Any]?) {
         analytics.group(groupId: groupId, traits: traits)
     }
 }
diff --git a/Sources/Segment/Utilities/JSON.swift b/Sources/Segment/Utilities/JSON.swift
index f08fdf09..1160bd77 100644
--- a/Sources/Segment/Utilities/JSON.swift
+++ b/Sources/Segment/Utilities/JSON.swift
@@ -274,9 +274,9 @@ extension JSON {
     /// - Parameters:
     ///   - keys: A dictionary containing key mappings, in the format of ["Old": "New"].
     ///   - valueTransform: An optional value transform closure.  Key represents the new key name.
-    public func mapKeys(_ keys: [String: String], valueTransform: ((_ key: String, _ value: Any) -> Any)? = nil) throws -> JSON {
+    public func mapTransform(_ keys: [String: String], valueTransform: ((_ key: String, _ value: Any) -> Any)? = nil) throws -> JSON {
         guard let dict = self.dictionaryValue else { return self }
-        let mapped = try dict.mapKeys(keys, valueTransform: valueTransform)
+        let mapped = try dict.mapTransform(keys, valueTransform: valueTransform)
         let result = try JSON(mapped)
         return result
     }
@@ -285,7 +285,7 @@ extension JSON {
 // MARK: - Helpers
 
 extension Dictionary where Key == String, Value == Any {
-    internal func mapKeys(_ keys: [String: String], valueTransform: ((_ key: Key, _ value: Value) -> Any)? = nil) throws -> [Key: Value] {
+    internal func mapTransform(_ keys: [String: String], valueTransform: ((_ key: Key, _ value: Value) -> Any)? = nil) throws -> [Key: Value] {
         let mapped = Dictionary(uniqueKeysWithValues: self.map { key, value -> (Key, Value) in
             var newKey = key
             var newValue = value
@@ -299,7 +299,7 @@ extension Dictionary where Key == String, Value == Any {
             }
             // is this value a dictionary?
             if let dictValue = value as? [Key: Value] {
-                if let r = try? dictValue.mapKeys(keys, valueTransform: valueTransform) {
+                if let r = try? dictValue.mapTransform(keys, valueTransform: valueTransform) {
                     // if so, lets recurse...
                     newValue = r
                 }
@@ -309,7 +309,7 @@ extension Dictionary where Key == String, Value == Any {
                 newValue = arrayValue.map { item -> Value in
                     var newValue = item
                     if let dictValue = item as? [Key: Value] {
-                        if let r = try? dictValue.mapKeys(keys, valueTransform: valueTransform) {
+                        if let r = try? dictValue.mapTransform(keys, valueTransform: valueTransform) {
                             newValue = r
                         }
                     }
@@ -318,7 +318,7 @@ extension Dictionary where Key == String, Value == Any {
             }
             
             if !(newValue is [Key: Value]), let transform = valueTransform {
-                // it's not a dictionary apply our transform.
+                // it's not a dictionary so apply our transform.
                 
                 // note: if it's an array, we've processed any dictionaries inside
                 // already, but this gives the opportunity to apply a transform to the other


Test patch:
diff --git a/Tests/Segment-Tests/JSON_Tests.swift b/Tests/Segment-Tests/JSON_Tests.swift
index ca02d477..1a847593 100644
--- a/Tests/Segment-Tests/JSON_Tests.swift
+++ b/Tests/Segment-Tests/JSON_Tests.swift
@@ -137,7 +137,7 @@ class JSONTests: XCTestCase {
         
         let json = try! JSON(dict)
         
-        let output = try! json.mapKeys(keys).dictionaryValue
+        let output = try! json.mapTransform(keys).dictionaryValue
         
         XCTAssertTrue(output!["AKey1"] as! Int == 1)
         XCTAssertTrue(output!["AKey2"] as! Int == 2)
@@ -153,7 +153,7 @@ class JSONTests: XCTestCase {
         
         let json = try! JSON(dict)
         
-        let output = try! json.mapKeys(keys, valueTransform: { key, value in
+        let output = try! json.mapTransform(keys, valueTransform: { key, value in
             var newValue = value
             if let v = newValue as? Int {
                 if v == 1 {