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.

Community
  • 1
  • 1
Matthias dirickx
  • 141
  • 2
  • 10

3 Answers3

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.

Matthias dirickx
  • 141
  • 2
  • 10
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())
Community
  • 1
  • 1
Rao
  • 20,781
  • 11
  • 57
  • 77
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

Rao
  • 20,781
  • 11
  • 57
  • 77
  • @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? – Rao May 02 '17 at 18:52
  • 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. – Matthias dirickx May 02 '17 at 18:53
  • The answer above is definitely not what I am looking for. Any hints to make the question more specific? – Matthias dirickx May 02 '17 at 18:55
  • Oh may be I over looked or did not pay right attention. Relooking at the question. With Eval, it is possible to extract value. But how do you know the path if it is dynamic? – Rao May 02 '17 at 18:57
  • I do want to thank you for your quick reply though. Thanks! There you taking the time to answer and I throw mud... – Matthias dirickx May 02 '17 at 19:02
  • @Matthiasdirickx, can you answer - how do you know the path if it is dynamic? – Rao May 02 '17 at 19:04
  • 1
    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. – Matthias dirickx May 02 '17 at 19:06
  • 1
    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... :) – Matthias dirickx May 02 '17 at 19:08
  • Thank you for the last 2 comments, useful info. 220 properties / variables? Isn't it too much to handle? Are you using data driven tests? Open source or ReadyAPI? – Rao May 02 '17 at 19:11
  • Open source, ReadyAPI should come to the table some day though. I need to nag a little for that. I do like the challenge though. So yeah, I am scripting the 'get a line from excel' part. That does indeed mean that I have defined all these variables to be assigned. This might seem weird, but I did that with a list in excel which I kind of used as a code generator to create all the 'context.testCase.setPropertValue("propNameParent.propNameChild", "value taken from excel")' lines. By now I probably would have managed to make the massive amount property transfers as well, but the script: better. – Matthias dirickx May 02 '17 at 19:18
  • It is very easy to deal with `.csv` in groovy using [xlson](https://github.com/xlson/groovycsv) instead of excel. And I do like script only and avoid Property Transfer step all the time. – Rao May 02 '17 at 19:21
  • Especially in this case I want to avoid this as well. I am not a programmer and while working on it I suspect that my logic is somewhat verbose (assigning to variable, then update the request) instead of updating the request directly when taking the variables. Thanks Rao. I'll take a look at that way. Still going to search for this solution though. I post it when I have it. – Matthias dirickx May 02 '17 at 19:56
  • 1
    @Matthiasdirickx, please see the other answer for your question. xlson is damn easy compared to excel, so advised that. – Rao May 02 '17 at 20:00