1

Anyone know how to efficiently set json in groovy with variable paths?

Context: I am working with soapui, a testing tool. Some tests are candidates to be data-driven. I have alot of variables. To make something sustainable that is easily implementable in similar circumstances, I would like a Groovy script that enables me to set variables.

I would name the variables 'parent.subParent.child'.

What I found:

I did find other things, but did not record them all.

The straight-forward thing I found was evaluation. With evaluation it was possible to get the values, but not the set them.

Eval.x(jsonbuilder, 'x.content.' + path) = 'newValue'

will return an error. But like I said, no problem retrieving the values in the json this way.

What I tried: I have got an implementation which works for one level. I can say:

jsonbuilder.content.parent.subParent[child] = 'newValue'

This will set the value of the requested entity.

Then I tried to expand this to an undefined number of levels.

//Assuming there is a jsonbuilder initialized
def jsonString = "{"parent":{"subParent":{"child":"oldValue"}}}"

def json = new JsonSlurper().parseText(jsonString)

def jsonbuilder = new JsonBuilder(json)

def path = 'parent.subParent.child'

def listPath = path.split("\\.")

def element = jsonbuilder.content

for(int i = 0; i < listPath.size(); i++) {
    element = element[listPath[i]]
}

element = 'newValue'

assert jsonbuilder.toString() == "{"parent":{"subParent":{"child":"newValue"}}}"

The issue: the value in the original json is not updated. Likely because I leave the jsonbuilder variable once I assign it to 'element' and continue with that entity.

That leaves me with two questions:

  • How do I get the element value in the original json?
  • More general: How do I update json with a variable path?

The rudimentary JSON assign function with jsonbuilder like this: jsonbuilder.content.parent.subParent.child = 'newValue' as given in one of the answers below is not what I am eyeing for. I am looking for a way to make the entire thing dynamic. I don't want to build a simple assignment, that already exists and works well. I am looking to build a machine that does the assignment for me, with the variable names parsed as the paths. Preferably within the groovy.json.* environment, but if I have to involve external libraries, so be it.

3 Answers 3

2

I was staring myself blind on a specific implementation of Eval. My solution was actually simple if I would have read the docs from the start.

You can find the docs for Eval here: http://docs.groovy-lang.org/2.4.7/html/api/groovy/util/Eval.html

Instead of trying to assign a value to an evaluated method/function, which is not logical now I think of if, you need to integrate everything into the evaluated expression. For what I find, you can have up to three variables you can use in you Eval function.

I only need two. I need the jsonbuilder object to be able to get the source of information. And I need to get the value to set. The path itself can be used as it exists because it is already what it needs to be with respect to the evaluation: a String.

The code:

import groovy.json.*

def jsonString = '{"parent":{"child":"oldValue"}}'
def newValue = 'newValue'
def stringPath = 'parent.child'

def json = new JsonSlurper().parseText(jsonString)
def jsonbuilder = new JsonBuilder(json)

Eval.xy(jsonbuilder, newValue, 'x.content.' + stringPath + '= y')

System.out.println(jsonbuilder.toString()=='{"parent":{"child":"newValue"}}')
System.out.println(jsonbuilder.content.parent.child == 'newValue')​​​​​​​

By using Eval.xy(objectOne, objectTwo, StringExpression), I am telling that I am passing a string to be evaluated as an expression, in which x represents objectOne and y represents objectTwo.

The code can be viewed in an online groovy script engine here: https://groovyconsole.appspot.com/edit/5202721384693760

Small disclaimer: I can't imagine using an evaluated expression in a code base that lets variables be randomly manipulated by the outside world. This expression, if used, will sit comfortably inside the context of my SoapUI project.

Sign up to request clarification or add additional context in comments.

1 Comment

This is much better and groovified.
1

Since you are willing to use library, json-path does that.

Credits to @kalle from here

  • Download the zip files from here
  • Extract the libraries and its dependencies from above zip
  • Copy them under SOAPUI_HOME/bin/ext directory
  • Restart SoapUI

Here you go:

import com.jayway.jsonpath.Configuration
import com.jayway.jsonpath.JsonPath
import com.jayway.jsonpath.spi.json.JacksonJsonNodeJsonProvider
import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider

Configuration configuration = Configuration.builder()
        .jsonProvider(new JacksonJsonNodeJsonProvider())
        .mappingProvider(new JacksonMappingProvider())
        .build()

//You need to prepend $. before the path which becomes valid jsonpath
def path = '$.parent.subParent.child'

def originalJson = """{
    "parent": {
        "subParent": {
            "child": "oldValue"
        }
    }
}"""

def updatedJson = JsonPath.using(configuration).parse(originalJson).set(path, 'newValue').json()

println(updatedJson.toString())

Comments

0

Here you go:

import groovy.json.JsonSlurper
import groovy.json.JsonBuilder

def jsonString = """{   "parent": {
  "subParent": {
   "child": "oldValue"
  }
}

}"""

def json = new JsonSlurper().parseText(jsonString)  
def jsonbuilder = new JsonBuilder(json)

//Assign the value for child with new value
jsonbuilder.content.parent.subParent.child = 'newValue'
println jsonbuilder.toPrettyString()​​​​​​​​​​

You can try online Demo

13 Comments

@Matthiasdirickx, appreciate up vote for helpful answer. If you think this is the best solution, consider accepting it as answer. Or are you looking for different solution?
But the question is how to make it dynamic. It is not always three levels, and the names are not known in advance.The example you are giving is what I set up now. So how would I make the path variable? It is possible to use variables when using brackets as indicated in the question body. But that does not allow for different levels.
As for your question, some more context. In SoapUI you can define variables. I want to to and extended tests with mandatory fields through a REST service. There are 220 fields, hence 220 variables. Instead of creating an assignment for each of them to be able to update the REST request message in SoapUI via an excel sheet, I though I could maybe name the variables after their path. I have to define them anyway. Then I could create a shorter script that would kind of auto-assign the variable to the correct place in the REST request.
It seemed handy to have such a mechanism for now, and in the future. It would facilitate ad-hoc testing of fields configurations inservices with a JSON POST message body (of which we have alot) without having to dive into assignments and technical specifications/scripts too much. This would make it more accesible to a larger public. This just to add some context. Maybe there is a more efficient approach to this and I am barking up the wrong three here... :)
@Matthiasdirickx, please see the other answer for your question. xlson is damn easy compared to excel, so advised that.
|

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.