Access multidimensional Array via String in groovy - groovy

I want to access a JSON HasMap via String like data['dimension1']['dimension2'] but i want to do that dynamically like data[myAccessor].
Code
import groovy.json.JsonSlurperClassic
dataContent = '''
{
"Test": {
"Info": "Hello"
}
}
'''
def jsonSlurper = new JsonSlurperClassic()
data = jsonSlurper.parseText(dataContent)
println data['Test']['Info'] // Prints 'Hello'
accessor = "'Test']['Info'"
println data[accessor] // Prints 'null'
'''

Yeah, you can't just execute complex text as code like that 😕 (or 😄, I can't decide)
The best you can do is to have accessor be something splittable like
accessor = 'Test->Info'
And then you can split this and "walk" down the input structure to get the result you're after like so:
import groovy.json.JsonSlurperClassic
dataContent = '''
{
"Test": {
"Info": "Hello"
}
}
'''
def jsonSlurper = new JsonSlurperClassic()
data = jsonSlurper.parseText(dataContent)
accessor = "Test->Info"
println accessor.split('->').inject(data) { currentData, accessorPart ->
currentData?.getAt(accessorPart)
}

You can leverage Groovy's Eval and represent the accessor with property notation to have something closer to your intent:
import groovy.json.JsonSlurperClassic
dataContent = '''
{
"Test": {
"Info": "Hello"
}
}
'''
def jsonSlurper = new JsonSlurperClassic()
data = jsonSlurper.parseText(dataContent)
println data.'Test'.'Info' // prints 'Hello'
accessor = "'Test'.'Info'"
Eval.x(data, "println x.${accessor}") // prints 'Hello'

Related

Is there a Groovy equivalent to Gradle findProperty?

I'm looking for a way to avoid having to check for the property first with hasProperty().
Ideally I would like to have something like
def a = myobj.getPropertyOrElse("mypropname", "defaultvalueifpropertymissing')
I see that gradle has a findProperty() but I can't find a similar thing for plain groovy.
The hasProperty method returns a MetaProperty instance that you can use to retrieve the value by passing the original instance:
def a = myobj.hasProperty('mypropname')?.getProperty(myobj) ?:
'defaultvalueifpropertymissing'
And then use the Safe navigation operator(?.) and Elvis operator (?:) to avoid the if/else.
The shortest version I could think of is
def a = if (a.hasProperty("mypropname")) a.getProperty("mypropname") else "defaultvalueifmissing"
which obviously repeats the property name twice. Creating your own method is possible but it's limited to your current class.
class MyClass {
String name = "the name"
}
def a = new MyClass()
def getProperty(Object theInstance, String propName, Object defaultValue) {
if (theInstance.hasProperty(propName)) theInstance.getProperty(propName) else defaultValue
}
assert "the name" == getProperty(a, "name", "")
assert "default value" == getProperty(a, "method", "default value")
One can use getProperties() of MetaClass or getProperty() of GroovyObject:
class Test {
String field1
String field2
}
def test = new Test(field1: "value1", field2: null)
// using MetaClass.getProperties()
println test.properties["field1"] // value1
println test.properties["field2"] // null
println "field2" in test.properties.keySet() // true
println test.properties["field3"] // null
println "field3" in test.properties.keySet() // false
// using GroovyObject.getProperty()
println test.getProperty("field1") // value1
println test.getProperty("field2") // null
println test.getProperty("field3") // groovy.lang.MissingPropertyException

In Groovy/Spock assert call methods are not executed

In Groovy Unit Test with Spock the following task is quite common:
assert myResult == calculateExpectedResult() (With or without the assert keyword.)
The groovy assert prints out lots of infomation on what is going on here and why my assertion failed. But when the compared objects are very complex and deep it can be tricky go get the concrete property that failed the test.
For this I found the Javers Framework that does an excellent Job comparing the objects and producing an exact diff. I created a trait to do this:
trait DiffTrait {
Javers javers = JaversBuilder.javers().build()
String diff(result, expected) {
Diff diff = javers.compare(result, expected);
def valueChanges = diff.getChangesByType(ValueChange)
String message = ""
valueChanges.each { message += "\n$it.propertyName = $it.left instead of expected: $it.right" }
return message
}
}
Now I can use it in my Unit Tests like this:
def expected = calculateExpectedResult()
assert myResult == expected, diff(myResult, expected)
This way I get a nicely printed list of differences.
But this is kind of verbose because I have to specify the values two times.
So I have changed the trait like this:
trait DiffTrait {
Javers javers = JaversBuilder.javers().build()
def result
def expected
String diff(result, expected) {
Diff diff = javers.compare(result, expected);
def valueChanges = diff.getChangesByType(ValueChange)
String message = ""
valueChanges.each { message += "\n$it.propertyName = $it.left instead of expected: $it.right" }
return message
}
String diff() {
diff(result, expected)
}
def result(result) {
this.result = result
return result
}
def expected(expected) {
this.expected = expected
return expected
}
}
The idea was to use it like this:
def result = callTheSystemToProduceTheRealResult()
def expected = calculateExpectedResult()
assert result(myResult) == expected(expected), diff()
But surprisingly this does not work! The two attributes are null and the diff Method fails with a NotNull-Exception. If I debug this code the expected/result methods are never called!
If I rewrite the code like this
def result = result(callTheSystemToProduceTheRealResult())
def expected = expected(calculateExpectedResult())
assert myResult == expected, diff()
everything works as expected. The methods get called correctly and the attributes are set.
My question is: Why can't I call these methods in the assert statement? What is the difference from the Groovy/Spock perspective of these two code fragements?
Here is a gist containing all the code as running example.
It is quite easy to explain. Assertion message is evaluated before the assertion itself. The following piece of code works perfectly, however it displays static diff message:
import org.javers.core.Javers
import org.javers.core.JaversBuilder
import org.javers.core.diff.Diff
import org.javers.core.diff.changetype.ValueChange
import spock.lang.Specification
class LolSpec extends Specification implements DiffTrait {
def 'lol'() {
expect:
def whatIGot = new Lol(l: 'a')
def whatIExpected = new Lol(l: 'b')
assert result(whatIGot) == expected(whatIExpected), 'diff'
}
}
trait DiffTrait {
Javers javers = JaversBuilder.javers().build()
def result
def expected
String diff() {
diff(result, expected)
}
String diff(result, expected) {
Diff diff = javers.compare(result, expected);
def valueChanges = diff.getChangesByType(ValueChange)
String message = ""
valueChanges.each { message += "\n$it.propertyName = $it.left instead of expected: $it.right" }
return message
}
def result(result) {
this.result = result
return result
}
def expected(expected) {
this.expected = expected
return expected
}
}
class Lol {
String l
}
You need to pass the arguments twice or change the implementation, e.g.:
import groovy.transform.EqualsAndHashCode
import org.javers.core.Javers
import org.javers.core.JaversBuilder
import org.javers.core.diff.changetype.ValueChange
import spock.lang.Specification
class LolSpec extends Specification {
def 'lol'() {
expect:
def whatIGot = new Lol(l: 'a')
def whatIExpected = new Lol(l: 'b')
def diff = new Diff(result: whatIGot, expected: whatIExpected)
assert diff.check(), diff.message()
}
}
class Diff {
Javers javers = JaversBuilder.javers().build()
def result
def expected
String message() {
def diff = javers.compare(result, expected);
def valueChanges = diff.getChangesByType(ValueChange)
String message = ""
valueChanges.each { message += "\n$it.propertyName = $it.left instead of expected: $it.right" }
return message
}
boolean check() {
result.equals(expected)
}
}
#EqualsAndHashCode
class Lol {
String l
}

Groovy map constructor keys to different variable names

I have JSON looking like:
{
"days": [
{
"mintemp": "21.8"
}
]
}
With Groovy, I parse it like this:
class WeatherRow {
String mintemp
}
def file = new File("data.json")
def slurper = new JsonSlurper().parse(file)
def days = slurper.days
def firstRow = days[0] as WeatherRow
println firstRow.mintemp
But actually, I would like to name my instance variable something like minTemp (or even something completely random, like numberOfPonies). Is there a way in Groovy to map a member of a map passed to a constructor to something else?
To clarify, I was looking for something along the lines of #XmlElement(name="mintemp"), but could not easily find it:
class WeatherRow {
#Element(name="mintemp")
String minTemp
}
Create a constructor that takes a map.
Runnable example:
import groovy.json.JsonSlurper
def testJsonStr = '''
{"days": [
{ "mintemp": "21.8" }
]}'''
class WeatherRow {
String minTemp
WeatherRow(map) {
println "Got called with constructor that takes a map: $map"
minTemp = map.mintemp
}
}
def slurper = new JsonSlurper().parseText(testJsonStr)
def days = slurper.days
def firstRow = days[0] as WeatherRow
println firstRow.minTemp
Result:
Got called with constructor that takes a map: [mintemp:21.8]
21.8
(of course you'd remove the println line, it's just there for the demo)
You can achieve this using annotation and simple custom annotation processor like this:
1. Create a Custom Annotation Class
#Retention(RetentionPolicy.RUNTIME)
#interface JsonDeserializer {
String[] names() default []
}
2. Annotate your instance fields with the custom annotation
class WeatherRow{
#JsonDeserializer(names = ["mintemp"])
String mintemp;
#JsonDeserializer(names = ["mintemp"])
String minTemp;
#JsonDeserializer(names = ["mintemp"])
String numberOfPonies;
}
3. Add custom json deserializer method using annotation processing:
static WeatherRow fromJson(def jsonObject){
WeatherRow weatherRow = new WeatherRow();
try{
weatherRow = new WeatherRow(jsonObject);
}catch(MissingPropertyException ex){
//swallow missing property exception.
}
WeatherRow.class.getDeclaredFields().each{
def jsonDeserializer = it.getDeclaredAnnotations()?.find{it.annotationType() == JsonDeserializer}
def fieldNames = [];
fieldNames << it.name;
if(jsonDeserializer){
fieldNames.addAll(jsonDeserializer.names());
fieldNames.each{i ->
if(jsonObject."$i")//TODO: if field type is not String type custom parsing here.
weatherRow."${it.name}" = jsonObject."$i";
}
}
};
return weatherRow;
}
Example:
def testJsonStr = '''
{
"days": [
{
"mintemp": "21.8"
}
]
}'''
def parsedWeatherRows = new JsonSlurper().parseText(testJsonStr);
assert WeatherRow.fromJson(parsedWeatherRows.days[0]).mintemp == "21.8"
assert WeatherRow.fromJson(parsedWeatherRows.days[0]).minTemp == "21.8"
assert WeatherRow.fromJson(parsedWeatherRows.days[0]).numberOfPonies == "21.8"
Check the full working code at groovyConsole.

Groovy object properties in map

Instead of having to declare all the properties in a map from an object like:
prop1: object.prop1
Can't you just drop the object in there like below somehow? Or what would be a proper way to achieve this?
results: [
object,
values: [
test: 'subject'
]
]
object.properties will give you a class as well
You should be able to do:
Given your POGO object:
class User {
String name
String email
}
def object = new User(name:'tim', email:'tim#tim.com')
Write a method to inspect the class and pull the non-synthetic properties from it:
def extractProperties(obj) {
obj.getClass()
.declaredFields
.findAll { !it.synthetic }
.collectEntries { field ->
[field.name, obj."$field.name"]
}
}
Then, map spread that into your result map:
def result = [
value: true,
*:extractProperties(object)
]
To give you:
['value':true, 'name':'tim', 'email':'tim#tim.com']
If you don't mind using a few libraries here's an option where you convert the object to json and then parse it back out as a map. I added mine to a baseObject which in your case object would extend.
class BaseObject {
Map asMap() {
def jsonSlurper = new groovy.json.JsonSlurperClassic()
Map map = jsonSlurper.parseText(this.asJson())
return map
}
String asJson(){
def jsonOutput = new groovy.json.JsonOutput()
String json = jsonOutput.toJson(this)
return json
}
}
Also wrote it without the json library originally. This is like the other answers but handles cases where the object property is a List.
class BaseObject {
Map asMap() {
Map map = objectToMap(this)
return map
}
def objectToMap(object){
Map map = [:]
for(item in object.class.declaredFields){
if(!item.synthetic){
if (object."$item.name".hasProperty('length')){
map."$item.name" = objectListToMap(object."$item.name")
}else if (object."$item.name".respondsTo('asMap')){
map << [ (item.name):object."$item.name"?.asMap() ]
} else{
map << [ (item.name):object."$item.name" ]
}
}
}
return map
}
def objectListToMap(objectList){
List list = []
for(item in objectList){
if (item.hasProperty('length')){
list << objectListToMap(item)
}else {
list << objectToMap(item)
}
}
return list
}
}
This seems to work well
*:object.properties

GPars: return of eachParallel{}

I want to do alot of stuff with each of those example strings and return Object of some other type here Integers, later some bigger class-objects.
Here in this example I am trying something simple, how ever I get a completly wrong result.
At least for what i was hoping to get back. xD
I hoped to get: [6, 5, 6, 5]
but instead I get: [butter, bread, dragon, table]
package test
#Grab(group='org.codehaus.gpars', module='gpars', version='1.0.0')
import static groovyx.gpars.GParsPool.withPool
class Test {
List<String> strings = new ArrayList<String>([
"butter",
"bread",
"dragon",
"table"
])
def closure = { it.length() }
def doStuff() {
def results = withPool( 4 ) {
strings.eachParallel{ it.length()}
}
println results
}
static main(args) {
def test = new Test()
test.doStuff()
}
}
It would be nice if the answer could have a short explanation.
Thanks a lot!
In groovy, each (and eachParallel in GPars) returns the original collection.
What you want is collect (to return the new collection made by calling the closure)
So, change
strings.eachParallel { it.length() }
to
strings.collectParallel { it.length() }
(btw)
GPars now comes bundled with Groovy so you shouldn't need the #Grab, and I assume you meant to use your closure variable in the collect?
package test
import static groovyx.gpars.GParsPool.withPool
class Test {
List<String> strings = [ "butter", "bread", "dragon", "table" ]
def closure = { it.length() }
def doStuff() {
def results = withPool( 4 ) {
strings.collectParallel closure
}
println results
}
static main( args ) {
def test = new Test()
test.doStuff()
}
}

Resources