Skip to content

Commit 6514588

Browse files
authored
Add new mapTransform function (#24)
* add tests + implementation for mapTransform * update docs * extract jsonArray mapTransform to its own + test
1 parent e3c7d1f commit 6514588

File tree

4 files changed

+221
-2
lines changed

4 files changed

+221
-2
lines changed

core/src/main/java/com/segment/analytics/kotlin/core/Configuration.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import java.util.concurrent.Executors
1717
* @property ioDispatcher Dispatcher running IO tasks, defaults to `Dispatchers.IO`
1818
* @property storageProvider Provider for storage class, defaults to `ConcreteStorageProvider`
1919
* @property collectDeviceId collect deviceId, defaults to `false`
20-
* @property trackApplicationLifecycleEvents automatically track Lifecycle events, defaults to `false`
20+
* @property trackApplicationLifecycleEvents automatically send track for Lifecycle events (eg: Application Opened, Application Backgrounded, etc.), defaults to `false`
2121
* @property useLifecycleObserver enables the use of LifecycleObserver to track Application lifecycle events. Defaults to `false`.
2222
* @property trackDeepLinks automatically track [Deep link][https://developer.android.com/training/app-links/deep-linking] opened based on intents, defaults to `false`
2323
* @property flushAt count of events at which we flush events, defaults to `20`

core/src/main/java/com/segment/analytics/kotlin/core/utilities/Base64Utils.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package com.segment.analytics.kotlin.core.utilities
33
// Encode string to base64
44
fun encodeToBase64(str: String) = encodeToBase64(str.toByteArray())
55

6-
// Encode byte-array to base64
6+
// Encode byte-array to base64, this implementation is not url-safe
77
fun encodeToBase64(bytes: ByteArray) = buildString {
88
val wData = ByteArray(3) // working data
99
var i = 0

core/src/main/java/com/segment/analytics/kotlin/core/utilities/JSON.kt

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
package com.segment.analytics.kotlin.core.utilities
22

3+
import kotlinx.serialization.json.JsonArray
34
import kotlinx.serialization.json.JsonElement
45
import kotlinx.serialization.json.JsonObject
56
import kotlinx.serialization.json.JsonObjectBuilder
67
import kotlinx.serialization.json.JsonPrimitive
78
import kotlinx.serialization.json.booleanOrNull
9+
import kotlinx.serialization.json.buildJsonArray
10+
import kotlinx.serialization.json.buildJsonObject
811
import kotlinx.serialization.json.contentOrNull
912
import kotlinx.serialization.json.doubleOrNull
1013
import kotlinx.serialization.json.floatOrNull
@@ -83,6 +86,57 @@ fun JsonObject.getMapSet(key: String): Set<Map<String, Any>>? {
8386
null
8487
}
8588

89+
// Utility function to apply key-mappings (deep traversal) and an optional value transform
90+
fun JsonObject.mapTransform(
91+
keyMapper: Map<String, String>,
92+
valueTransform: ((key: String, value: JsonElement) -> JsonElement)? = null
93+
): JsonObject = buildJsonObject {
94+
val original = this@mapTransform
95+
original.forEach { (key, value) ->
96+
var newKey: String = key
97+
var newVal: JsonElement = value
98+
// does this key1 have a mapping?
99+
keyMapper[key]?.let { mappedKey ->
100+
newKey = mappedKey
101+
}
102+
103+
// is this value a dictionary?
104+
if (value is JsonObject) {
105+
// if so, lets recurse...
106+
newVal = value.mapTransform(keyMapper, valueTransform)
107+
} else if (value is JsonArray) {
108+
newVal = value.mapTransform(keyMapper, valueTransform)
109+
}
110+
if (newVal !is JsonObject && valueTransform != null) {
111+
// it's not a dictionary apply our transform.
112+
// note: if it's an array, we've processed any dictionaries inside
113+
// already, but this gives the opportunity to apply a transform to the other
114+
// items in the array that weren't dictionaries.
115+
116+
newVal = valueTransform(newKey, newVal)
117+
}
118+
put(newKey, newVal)
119+
}
120+
}
121+
122+
// Utility function to apply key-mappings (deep traversal) and an optional value transform
123+
fun JsonArray.mapTransform(
124+
keyMapper: Map<String, String>,
125+
valueTransform: ((key: String, value: JsonElement) -> JsonElement)? = null
126+
): JsonArray = buildJsonArray {
127+
val original = this@mapTransform
128+
original.forEach { item: JsonElement ->
129+
var newValue = item
130+
if (item is JsonObject) {
131+
newValue = item.mapTransform(keyMapper, valueTransform)
132+
} else if (item is JsonArray) {
133+
newValue = item.mapTransform(keyMapper, valueTransform)
134+
}
135+
add(newValue)
136+
}
137+
}
138+
139+
86140
// Utility function to transform keys in JsonObject. Only acts on root level keys
87141
fun JsonObject.transformKeys(transform: (String) -> String): JsonObject {
88142
return JsonObject(this.mapKeys { transform(it.key) })

core/src/test/kotlin/com/segment/analytics/kotlin/core/JSONTests.kt

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import com.segment.analytics.kotlin.core.utilities.getInt
66
import com.segment.analytics.kotlin.core.utilities.getMapSet
77
import com.segment.analytics.kotlin.core.utilities.getString
88
import com.segment.analytics.kotlin.core.utilities.getStringSet
9+
import com.segment.analytics.kotlin.core.utilities.mapTransform
10+
import com.segment.analytics.kotlin.core.utilities.toContent
911
import com.segment.analytics.kotlin.core.utilities.transformKeys
1012
import com.segment.analytics.kotlin.core.utilities.transformValues
1113
import kotlinx.serialization.json.*
@@ -337,4 +339,167 @@ class JSONTests {
337339
assertEquals("M", getString("Mr. Freeze"))
338340
}
339341
}
342+
343+
@Test
344+
fun `can map keys + nested keys using mapTransform`() {
345+
val keyMapper = mapOf(
346+
"item" to "\$item",
347+
"phone" to "\$phone",
348+
"name" to "\$name",
349+
)
350+
val map = buildJsonObject {
351+
put("company", buildJsonObject {
352+
put("phone", "123-456-7890")
353+
put("name", "Wayne Industries")
354+
})
355+
put("family", buildJsonArray {
356+
add(buildJsonObject { put("name", "Mary") })
357+
add(buildJsonObject { put("name", "Thomas") })
358+
})
359+
put("name", "Bruce")
360+
put("last_name", "wayne")
361+
put("item", "Grapple")
362+
}
363+
val newMap = map.mapTransform(keyMapper)
364+
with(newMap) {
365+
assertTrue(containsKey("\$name"))
366+
assertTrue(containsKey("\$item"))
367+
assertTrue(containsKey("last_name"))
368+
with(get("company")!!.jsonObject) {
369+
assertTrue(containsKey("\$phone"))
370+
assertTrue(containsKey("\$name"))
371+
}
372+
with(get("family")!!.jsonArray) {
373+
assertTrue(get(0).jsonObject.containsKey("\$name"))
374+
assertTrue(get(1).jsonObject.containsKey("\$name"))
375+
}
376+
}
377+
}
378+
379+
@Test
380+
fun `can transform values using mapTransform`() {
381+
val map = buildJsonObject {
382+
put("company", buildJsonObject {
383+
put("phone", "123-456-7890")
384+
put("name", "Wayne Industries")
385+
})
386+
put("family", buildJsonArray {
387+
add(buildJsonObject { put("name", "Mary") })
388+
add(buildJsonObject { put("name", "Thomas") })
389+
})
390+
put("name", "Bruce")
391+
put("last_name", "wayne")
392+
put("item", "Grapple")
393+
}
394+
val newMap = map.mapTransform(emptyMap()) { newKey, value ->
395+
var newVal = value
396+
if (newKey == "phone") {
397+
val foo = value.jsonPrimitive.toContent()
398+
if (foo is String) {
399+
newVal = JsonPrimitive(foo.replace("-", ""))
400+
}
401+
}
402+
newVal
403+
}
404+
with(newMap) {
405+
with(get("company")!!.jsonObject) {
406+
assertEquals("1234567890", getString("phone"))
407+
}
408+
}
409+
}
410+
411+
@Test
412+
fun `can map keys + transform values using mapTransform`() {
413+
val keyMapper = mapOf(
414+
"item" to "\$item",
415+
"phone" to "\$phone",
416+
"name" to "\$name",
417+
)
418+
val map = buildJsonObject {
419+
put("company", buildJsonObject {
420+
put("phone", "123-456-7890")
421+
put("name", "Wayne Industries")
422+
})
423+
put("family", buildJsonArray {
424+
add(buildJsonObject { put("name", "Mary") })
425+
add(buildJsonObject { put("name", "Thomas") })
426+
})
427+
put("name", "Bruce")
428+
put("last_name", "wayne")
429+
put("item", "Grapple")
430+
}
431+
val newMap = map.mapTransform(keyMapper) { newKey, value ->
432+
var newVal = value
433+
if (newKey == "\$phone") {
434+
val foo = value.jsonPrimitive.toContent()
435+
if (foo is String) {
436+
newVal = JsonPrimitive(foo.replace("-", ""))
437+
}
438+
}
439+
newVal
440+
}
441+
with(newMap) {
442+
assertTrue(containsKey("\$name"))
443+
assertTrue(containsKey("\$item"))
444+
assertTrue(containsKey("last_name"))
445+
with(get("company")!!.jsonObject) {
446+
assertTrue(containsKey("\$phone"))
447+
assertTrue(containsKey("\$name"))
448+
assertEquals("1234567890", getString("\$phone"))
449+
}
450+
with(get("family")!!.jsonArray) {
451+
assertTrue(get(0).jsonObject.containsKey("\$name"))
452+
assertTrue(get(1).jsonObject.containsKey("\$name"))
453+
}
454+
}
455+
}
456+
457+
@Test
458+
fun `can map keys + transform values using mapTransform on JsonArray`() {
459+
val keyMapper = mapOf(
460+
"item" to "\$item",
461+
"phone" to "\$phone",
462+
"name" to "\$name",
463+
)
464+
val list = buildJsonArray {
465+
add(buildJsonObject {
466+
put("phone", "123-456-7890")
467+
put("name", "Wayne Industries")
468+
})
469+
add(buildJsonArray {
470+
add(buildJsonObject { put("name", "Mary") })
471+
add(buildJsonObject { put("name", "Thomas") })
472+
})
473+
add(buildJsonObject {
474+
put("name", "Bruce")
475+
put("last_name", "wayne")
476+
put("item", "Grapple")
477+
})
478+
}
479+
val newList = list.mapTransform(keyMapper) { newKey, value ->
480+
var newVal = value
481+
if (newKey == "\$phone") {
482+
val foo = value.jsonPrimitive.toContent()
483+
if (foo is String) {
484+
newVal = JsonPrimitive(foo.replace("-", ""))
485+
}
486+
}
487+
newVal
488+
}
489+
with(newList) {
490+
get(0).jsonObject.let {
491+
assertTrue(it.containsKey("\$phone"))
492+
assertTrue(it.containsKey("\$name"))
493+
}
494+
get(1).jsonArray.let {
495+
assertEquals(buildJsonObject { put("\$name", "Mary") }, it[0])
496+
assertEquals(buildJsonObject { put("\$name", "Thomas") }, it[1])
497+
}
498+
get(2).jsonObject.let {
499+
assertTrue(it.containsKey("\$name"))
500+
assertTrue(it.containsKey("\$item"))
501+
assertTrue(it.containsKey("last_name"))
502+
}
503+
}
504+
}
340505
}

0 commit comments

Comments
 (0)