Skip to content

Add new mapTransform function #24

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import java.util.concurrent.Executors
* @property ioDispatcher Dispatcher running IO tasks, defaults to `Dispatchers.IO`
* @property storageProvider Provider for storage class, defaults to `ConcreteStorageProvider`
* @property collectDeviceId collect deviceId, defaults to `false`
* @property trackApplicationLifecycleEvents automatically track Lifecycle events, defaults to `false`
* @property trackApplicationLifecycleEvents automatically send track for Lifecycle events (eg: Application Opened, Application Backgrounded, etc.), defaults to `false`
* @property useLifecycleObserver enables the use of LifecycleObserver to track Application lifecycle events. Defaults to `false`.
* @property trackDeepLinks automatically track [Deep link][https://developer.android.com/training/app-links/deep-linking] opened based on intents, defaults to `false`
* @property flushAt count of events at which we flush events, defaults to `20`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package com.segment.analytics.kotlin.core.utilities
// Encode string to base64
fun encodeToBase64(str: String) = encodeToBase64(str.toByteArray())

// Encode byte-array to base64
// Encode byte-array to base64, this implementation is not url-safe
fun encodeToBase64(bytes: ByteArray) = buildString {
val wData = ByteArray(3) // working data
var i = 0
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package com.segment.analytics.kotlin.core.utilities

import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonObjectBuilder
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.booleanOrNull
import kotlinx.serialization.json.buildJsonArray
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.contentOrNull
import kotlinx.serialization.json.doubleOrNull
import kotlinx.serialization.json.floatOrNull
Expand Down Expand Up @@ -83,6 +86,57 @@ fun JsonObject.getMapSet(key: String): Set<Map<String, Any>>? {
null
}

// Utility function to apply key-mappings (deep traversal) and an optional value transform
fun JsonObject.mapTransform(
keyMapper: Map<String, String>,
valueTransform: ((key: String, value: JsonElement) -> JsonElement)? = null
): JsonObject = buildJsonObject {
val original = this@mapTransform
original.forEach { (key, value) ->
var newKey: String = key
var newVal: JsonElement = value
// does this key1 have a mapping?
keyMapper[key]?.let { mappedKey ->
newKey = mappedKey
}

// is this value a dictionary?
if (value is JsonObject) {
// if so, lets recurse...
newVal = value.mapTransform(keyMapper, valueTransform)
} else if (value is JsonArray) {
newVal = value.mapTransform(keyMapper, valueTransform)
}
if (newVal !is JsonObject && valueTransform != null) {
// it's not a dictionary 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
// items in the array that weren't dictionaries.

newVal = valueTransform(newKey, newVal)
}
put(newKey, newVal)
}
}

// Utility function to apply key-mappings (deep traversal) and an optional value transform
fun JsonArray.mapTransform(
keyMapper: Map<String, String>,
valueTransform: ((key: String, value: JsonElement) -> JsonElement)? = null
): JsonArray = buildJsonArray {
val original = this@mapTransform
original.forEach { item: JsonElement ->
var newValue = item
if (item is JsonObject) {
newValue = item.mapTransform(keyMapper, valueTransform)
} else if (item is JsonArray) {
newValue = item.mapTransform(keyMapper, valueTransform)
}
add(newValue)
}
}


// Utility function to transform keys in JsonObject. Only acts on root level keys
fun JsonObject.transformKeys(transform: (String) -> String): JsonObject {
return JsonObject(this.mapKeys { transform(it.key) })
Expand Down
165 changes: 165 additions & 0 deletions core/src/test/kotlin/com/segment/analytics/kotlin/core/JSONTests.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import com.segment.analytics.kotlin.core.utilities.getInt
import com.segment.analytics.kotlin.core.utilities.getMapSet
import com.segment.analytics.kotlin.core.utilities.getString
import com.segment.analytics.kotlin.core.utilities.getStringSet
import com.segment.analytics.kotlin.core.utilities.mapTransform
import com.segment.analytics.kotlin.core.utilities.toContent
import com.segment.analytics.kotlin.core.utilities.transformKeys
import com.segment.analytics.kotlin.core.utilities.transformValues
import kotlinx.serialization.json.*
Expand Down Expand Up @@ -337,4 +339,167 @@ class JSONTests {
assertEquals("M", getString("Mr. Freeze"))
}
}

@Test
fun `can map keys + nested keys using mapTransform`() {
val keyMapper = mapOf(
"item" to "\$item",
"phone" to "\$phone",
"name" to "\$name",
)
val map = buildJsonObject {
put("company", buildJsonObject {
put("phone", "123-456-7890")
put("name", "Wayne Industries")
})
put("family", buildJsonArray {
add(buildJsonObject { put("name", "Mary") })
add(buildJsonObject { put("name", "Thomas") })
})
put("name", "Bruce")
put("last_name", "wayne")
put("item", "Grapple")
}
val newMap = map.mapTransform(keyMapper)
with(newMap) {
assertTrue(containsKey("\$name"))
assertTrue(containsKey("\$item"))
assertTrue(containsKey("last_name"))
with(get("company")!!.jsonObject) {
assertTrue(containsKey("\$phone"))
assertTrue(containsKey("\$name"))
}
with(get("family")!!.jsonArray) {
assertTrue(get(0).jsonObject.containsKey("\$name"))
assertTrue(get(1).jsonObject.containsKey("\$name"))
}
}
}

@Test
fun `can transform values using mapTransform`() {
val map = buildJsonObject {
put("company", buildJsonObject {
put("phone", "123-456-7890")
put("name", "Wayne Industries")
})
put("family", buildJsonArray {
add(buildJsonObject { put("name", "Mary") })
add(buildJsonObject { put("name", "Thomas") })
})
put("name", "Bruce")
put("last_name", "wayne")
put("item", "Grapple")
}
val newMap = map.mapTransform(emptyMap()) { newKey, value ->
var newVal = value
if (newKey == "phone") {
val foo = value.jsonPrimitive.toContent()
if (foo is String) {
newVal = JsonPrimitive(foo.replace("-", ""))
}
}
newVal
}
with(newMap) {
with(get("company")!!.jsonObject) {
assertEquals("1234567890", getString("phone"))
}
}
}

@Test
fun `can map keys + transform values using mapTransform`() {
val keyMapper = mapOf(
"item" to "\$item",
"phone" to "\$phone",
"name" to "\$name",
)
val map = buildJsonObject {
put("company", buildJsonObject {
put("phone", "123-456-7890")
put("name", "Wayne Industries")
})
put("family", buildJsonArray {
add(buildJsonObject { put("name", "Mary") })
add(buildJsonObject { put("name", "Thomas") })
})
put("name", "Bruce")
put("last_name", "wayne")
put("item", "Grapple")
}
val newMap = map.mapTransform(keyMapper) { newKey, value ->
var newVal = value
if (newKey == "\$phone") {
val foo = value.jsonPrimitive.toContent()
if (foo is String) {
newVal = JsonPrimitive(foo.replace("-", ""))
}
}
newVal
}
with(newMap) {
assertTrue(containsKey("\$name"))
assertTrue(containsKey("\$item"))
assertTrue(containsKey("last_name"))
with(get("company")!!.jsonObject) {
assertTrue(containsKey("\$phone"))
assertTrue(containsKey("\$name"))
assertEquals("1234567890", getString("\$phone"))
}
with(get("family")!!.jsonArray) {
assertTrue(get(0).jsonObject.containsKey("\$name"))
assertTrue(get(1).jsonObject.containsKey("\$name"))
}
}
}

@Test
fun `can map keys + transform values using mapTransform on JsonArray`() {
val keyMapper = mapOf(
"item" to "\$item",
"phone" to "\$phone",
"name" to "\$name",
)
val list = buildJsonArray {
add(buildJsonObject {
put("phone", "123-456-7890")
put("name", "Wayne Industries")
})
add(buildJsonArray {
add(buildJsonObject { put("name", "Mary") })
add(buildJsonObject { put("name", "Thomas") })
})
add(buildJsonObject {
put("name", "Bruce")
put("last_name", "wayne")
put("item", "Grapple")
})
}
val newList = list.mapTransform(keyMapper) { newKey, value ->
var newVal = value
if (newKey == "\$phone") {
val foo = value.jsonPrimitive.toContent()
if (foo is String) {
newVal = JsonPrimitive(foo.replace("-", ""))
}
}
newVal
}
with(newList) {
get(0).jsonObject.let {
assertTrue(it.containsKey("\$phone"))
assertTrue(it.containsKey("\$name"))
}
get(1).jsonArray.let {
assertEquals(buildJsonObject { put("\$name", "Mary") }, it[0])
assertEquals(buildJsonObject { put("\$name", "Thomas") }, it[1])
}
get(2).jsonObject.let {
assertTrue(it.containsKey("\$name"))
assertTrue(it.containsKey("\$item"))
assertTrue(it.containsKey("last_name"))
}
}
}
}