The purpose of the code is to apply adapter pattern to put an extra feature for searching through json object. I hate deep nested json object because it makes my code look messy when access through each node. I made an adapter class that can search through this deep json. So basically instead of having this messy code
val result = jsonObject.getAsJsonObject("user")
.getAsJsonObject("address")
.getAsJsonObject("home")
.getAsJsonArray("rooms")
.first()
.asJsonObject
.getAsJsonObject("another_one")
.getAsJsonArray("and_another")
.first()
.asString // etc...
you will have nice and clean query string in one line.
val query = "user.address.home.rooms[0].another_one.and_another[0]"
val result = JsonObjectAdapter(jsonObject).search<String>(query)
My adapter class
/**
* Adapter for any json object class
*
* @param T type of json object
* @property json target json object
*/
abstract class JsonAdapter<T> {
var json: T
constructor(json: T) {
this.json = json
}
/**
* search through each json node and return the result
*
* @param query string to search the json
* @param R type of result
*/
inline fun <reified R> search(query: String): R {
val keys = query.split(".")
val traverseKey = keys.subList(0, keys.size - 1)
val lastNode = traverseKey.fold(json) { acc, field ->
val pattern = Regex("(.*?)[(\\d+)]")
if (pattern.matches(field)) {
val group = pattern.find(field)!!.groupValues
val target = group.first()
val index = group.last() as Int
parseJsonArray(acc, target, index)
}
else {
parseJsonObject(acc, field)
}
}
return parseResult(lastNode, keys.last(), R::class)
}
/**
* parse json array format according to the json object type
*
* @param node current json node
* @param key key to retrieve the next node
* @param index target array index
*/
abstract fun parseJsonArray(node: T, key: String, index: Int): T;
/**
* parse json array format according to the json object type
*
* @param node current json node
* @param key key to retrieve the next node
*/
abstract fun parseJsonObject(node: T, key: String): T;
/**
* parse the result to the result type
*
* @param lastNode last json node
* @param key key to retrieve value
*/
abstract fun <R> parseResult(lastNode: T, key: String, type: KClass<*>): R;
}
class JsonObjectAdapter : JsonAdapter<JsonObject> {
constructor(json: JsonObject): super(json) {
this.json = json
}
override fun parseJsonArray(node: JsonObject, key: String, index: Int): JsonObject {
return node.getAsJsonArray(key)[index].asJsonObject
}
override fun parseJsonObject(node: JsonObject, key: String): JsonObject {
return node.getAsJsonObject(key)
}
override fun <R> parseResult(lastNode: JsonObject, key: String, type: KClass<*>): R {
return when(type) {
Int::class -> lastNode.get(key).asNumber as R
else -> lastNode.get(key).asString as R
}
}
}