2
\$\begingroup\$

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
      }
    }
}
\$\endgroup\$
1
  • 2
    \$\begingroup\$ Please edit your question so that the title describes the purpose of the code, rather than its mechanism. We really need to understand the motivational context to give good reviews. Thanks! \$\endgroup\$ Mar 17, 2022 at 8:00

1 Answer 1

3
\$\begingroup\$

Instead of using the adapter pattern, you can use the power of Kotlin's extension functions to accomplish what you want.

fun <reified R> JsonObject.search(query: String): R {
    // You can use `this.` here and apply any magic that you want
    // You can even call other methods here and pass `this` along
}

If you were to use the Jackson library, they have a built-in function for this:

val mapper = jacksonObjectMapper()
val tree = mapper.readTree("""{ "hello": { "world": [{}, { "here": { "i": { "am": 42 } } }] } }""")
println(tree.at("/hello")) // Returns an ObjectNode
println(tree.at("/hello/world")) // Returns an ArrayNode
println(tree.at("/hello/world/1")) // Returns an ObjectNode
println(tree.at("/hello/world/1/here")) // Returns an ObjectNode
println(tree.at("/hello/world/1/here/i")) // Returns an ObjectNode
println(tree.at("/hello/world/1/here/i/am").asInt()) // Prints 42
\$\endgroup\$
2
  • \$\begingroup\$ That's great idea will take a note! \$\endgroup\$
    – Patrick
    Mar 17, 2022 at 12:13
  • \$\begingroup\$ @Patrick Also added a note about existing functionality in the Jackson library that you can use out of the box \$\endgroup\$ Jul 6, 2022 at 10:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.