0

I am using Gson to parse JSON in my application. I have a particular use case where I want to take a JsonObject and effectively deep clone it, except alter the a key/value that matches some particular criteria.

For example, imagine the source object is something like this:

{
  "foo": {
    "bar": {
      "baz": "some value here"
    },
    "baz": "another value here"
  }
}

I want to iterate through every key (regardless how nested it might be) and if there is a key called baz I'll run my transformation function, and my output object would look like:

{
  "foo": {
    "bar": {
      "bazTransformed": "this got altered by my function"
    },
    "bazTransformed": "so did this"
  }
}

I'm aware I could do something like convert the JsonObject to string and use a RegEx pattern to find and replace, but that doesn't feel right.

I'm really struggling to get my head around creating a recursive function, or at least a better solution than string manipulation.

I can start the iteration with JsonObject.entrySet() but this returns a Map<String, JsonElement> - that seems to add more complexity because I'd need to check if the JsonElement is a JsonObject first before somehow continuing to recurse.

EDIT: To me it seems best to convert the JsonObject to a Map like so: gson.fromJson(sourceObj, Map::class.java) as MutableMap<*, *>

I can write a function that iterates recursively like so:

fun generateObject(sourceObj: JsonElement): JsonObject {
    val inputMap = gson.fromJson(sourceObj, Map::class.java) as MutableMap<*, *>

    val outputMap: MutableMap<String, Any> = mutableMapOf()

    fun go(toReturn: MutableMap<String,Any>,
           input: MutableMap<String, Any>) {
            for ((key, value) in input) {
                if (key == "baz") {
                    println("baz key found")
                    //do my transformation here

                }
                if (value is Map<*, *>) {
                    println("nested map")
                    go(toReturn, value as MutableMap<String, Any>)
                }
                // this part is wrong however, because `key` is potentially nested
                outputMap[key] = value
            }

    }

    go(outputMap, inputMap as MutableMap<String, Any>)

    return gson.toJsonTree(outputMap).asJsonObject
}
oxnard
  • 7
  • 3
  • Your suggestion with `entrySet` is most probably the best way to implement this. – Alexey Soshin Feb 26 '21 at 16:47
  • Have a look at JsonPath, a DSL for such kind of problems: https://stackoverflow.com/questions/27244431/how-to-change-values-in-a-json-file-using-xpath-jsonpath-in-java – terrorrussia-keeps-killing Feb 26 '21 at 21:07
  • Thanks @fluffy - I had a look at JsonPath and for that to work for me I think I'd need to traverse through the tree, constructing a string path as I go to later pass to JsonPath. For exmape: `foo.bar.baz`. However, I was thinking that if I am already able to iterate over each node recursively, then I should be able to build an object as I got (either a Gson JsonObject, or even a `Map<*, *>` and then convert that a JsonObject with Gson. – oxnard Mar 02 '21 at 08:36

1 Answers1

0

I found an answer to this problem thanks to the help of a friend. Hopefully this will help out someone else in the future with the same issue.

  val inputMap = mapOf(
      "foo" to mapOf(
          "bar" to mapOf(
              "baz" to "something composable")))

  val expectedOutputMap = mutableMapOf(
      "foo" to mutableMapOf(
          "bar" to mutableMapOf(
              "bux" to "here be some data")))
    
 
    
    fun go(input: Map<String, Any>) : Map<String, Any> {
      return input.entries.associate {
        if (it.key == "baz") {
          // alternatively, call some transformation function here
            "bux" to "here be some data"
        } else if ( it.value is Map<*, *>) {
            it.key to go(it.value as Map<String, Any>)
        } else {
            it.key to it.value                        
        }

         
      }
    }

    val outputMap = go(inputMap)

My use case evolved slightly differently in the end. For my use case I needed to actually take a JsonObject, iterate over it and if I found a particular key, then I would read the contents of that key and build a new JsonElement that would stand in it's place.

The solution I provide here glosses over that detail, because it's a bit of a detour, but you can see where such transformations would occur.

oxnard
  • 7
  • 3