Skip to content

Commit dc0e797

Browse files
bsneedBrandon Sneed
andauthored
Adds key mapping support for use in destination plugins. (#38)
* Adds key mapping support for use in destination plugins. * Slight cleanup. * Added array processing to tests. Co-authored-by: Brandon Sneed <[email protected]>
1 parent 0dbdd7d commit dc0e797

File tree

2 files changed

+111
-0
lines changed

2 files changed

+111
-0
lines changed

Sources/Segment/Utilities/JSON.swift

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,9 +267,73 @@ extension JSON {
267267
}
268268
}
269269

270+
// MARK: - Mapping
271+
272+
extension JSON {
273+
/// Maps keys supplied, in the format of ["Old": "New"]. Gives an optional value transformer that can be used to transform values based on the final key name.
274+
/// - Parameters:
275+
/// - keys: A dictionary containing key mappings, in the format of ["Old": "New"].
276+
/// - valueTransform: An optional value transform closure. Key represents the new key name.
277+
public func mapKeys(_ keys: [String: String], valueTransform: ((_ key: String, _ value: Any) -> Any)? = nil) throws -> JSON {
278+
guard let dict = self.dictionaryValue else { return self }
279+
let mapped = try dict.mapKeys(keys, valueTransform: valueTransform)
280+
let result = try JSON(mapped)
281+
return result
282+
}
283+
}
270284

271285
// MARK: - Helpers
272286

287+
extension Dictionary where Key == String, Value == Any {
288+
internal func mapKeys(_ keys: [String: String], valueTransform: ((_ key: Key, _ value: Value) -> Any)? = nil) throws -> [Key: Value] {
289+
let mapped = Dictionary(uniqueKeysWithValues: self.map { key, value -> (Key, Value) in
290+
var newKey = key
291+
var newValue = value
292+
293+
// does this key have a mapping?
294+
if keys.keys.contains(key) {
295+
if let mappedKey = keys[key] {
296+
// if so, lets change the key to the new value.
297+
newKey = mappedKey
298+
}
299+
}
300+
// is this value a dictionary?
301+
if let dictValue = value as? [Key: Value] {
302+
if let r = try? dictValue.mapKeys(keys, valueTransform: valueTransform) {
303+
// if so, lets recurse...
304+
newValue = r
305+
}
306+
} else if let arrayValue = value as? [Value] {
307+
// if it's an array, we need to see if any dictionaries are within and process
308+
// those as well.
309+
newValue = arrayValue.map { item -> Value in
310+
var newValue = item
311+
if let dictValue = item as? [Key: Value] {
312+
if let r = try? dictValue.mapKeys(keys, valueTransform: valueTransform) {
313+
newValue = r
314+
}
315+
}
316+
return newValue
317+
}
318+
}
319+
320+
if !(newValue is [Key: Value]), let transform = valueTransform {
321+
// it's not a dictionary apply our transform.
322+
323+
// note: if it's an array, we've processed any dictionaries inside
324+
// already, but this gives the opportunity to apply a transform to the other
325+
// items in the array that weren't dictionaries.
326+
newValue = transform(newKey, newValue)
327+
}
328+
329+
return (newKey, newValue)
330+
})
331+
332+
return mapped
333+
}
334+
}
335+
336+
273337
fileprivate extension NSNumber {
274338
static let trueValue = NSNumber(value: true)
275339
static let trueObjCType = trueValue.objCType

Tests/Segment-Tests/JSON_Tests.swift

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,4 +130,51 @@ class JSONTests: XCTestCase {
130130

131131
XCTAssertTrue(fetchedTraits?.email == "[email protected]")
132132
}
133+
134+
func testKeyMapping() {
135+
let keys = ["Key1": "AKey1", "Key2": "AKey2"]
136+
let dict: [String: Any] = ["Key1": 1, "Key2": 2, "Key3": 3, "Key4": ["Key1": 1]]
137+
138+
let json = try! JSON(dict)
139+
140+
let output = try! json.mapKeys(keys).dictionaryValue
141+
142+
XCTAssertTrue(output!["AKey1"] as! Int == 1)
143+
XCTAssertTrue(output!["AKey2"] as! Int == 2)
144+
XCTAssertTrue(output!["Key3"] as! Int == 3)
145+
146+
let subDict = output!["Key4"] as! [String: Any]
147+
XCTAssertTrue(subDict["AKey1"] as! Int == 1)
148+
}
149+
150+
func testKeyMappingWithValueTransform() {
151+
let keys = ["Key1": "AKey1", "Key2": "AKey2"]
152+
let dict: [String: Any] = ["Key1": 1, "Key2": 2, "Key3": 3, "Key4": ["Key1": 1], "Key5": [1, 2, ["Key1": 1]]]
153+
154+
let json = try! JSON(dict)
155+
156+
let output = try! json.mapKeys(keys, valueTransform: { key, value in
157+
var newValue = value
158+
if let v = newValue as? Int {
159+
if v == 1 {
160+
newValue = 11
161+
}
162+
}
163+
print("value = \(value.self)")
164+
return newValue
165+
}).dictionaryValue
166+
167+
XCTAssertTrue(output!["AKey1"] as! Int == 11)
168+
XCTAssertTrue(output!["AKey2"] as! Int == 2)
169+
XCTAssertTrue(output!["Key3"] as! Int == 3)
170+
171+
let subDict = output!["Key4"] as! [String: Any]
172+
XCTAssertTrue(subDict["AKey1"] as! Int == 11)
173+
174+
let subArray = output!["Key5"] as! [Any]
175+
let subArrayDict = subArray[2] as! [String: Any]
176+
XCTAssertTrue(subArray[0] as! Int == 1)
177+
XCTAssertTrue(subArray[1] as! Int == 2)
178+
XCTAssertTrue(subArrayDict["AKey1"] as! Int == 11)
179+
}
133180
}

0 commit comments

Comments
 (0)