Return Nested Key in Groovy - groovy

I am trying to determine the best way to return nested key values using groovy. If I have a map:
def map = [
OrganizationName: 'SampleTest',
Address: [
Street: '123 Sample St',
PostalCode: '00000',
]
]
Is there a way to return all of the keys? OrganizationName, OrganizationURL, Address.Street, Address.PostalCode? If I didn't have an map within a map I could use map.keySet() as String[]. Should I just loop through each key and see if it is an instanceof another map?

The Groovy libraries don't provide a method for this, but you can write your own. Here's an example that you can copy-paste into the Groovy console
List<String> getNestedMapKeys(Map map, String keyPrefix = '') {
def result = []
map.each { key, value ->
if (value instanceof Map) {
result += getNestedMapKeys(value, keyPrefix += "$key.")
} else {
result << "$keyPrefix$key"
}
}
result
}
// test it out
def map = [
OrganizationName: 'SampleTest',
Address: [
Street: '123 Sample St',
PostalCode: '00000',
]
]
assert ['OrganizationName', 'Address.Street', 'Address.PostalCode'] == getNestedMapKeys(map)

Use Following generic recursion Method To generate the list of all nested map keys
def getListOfKeys(def map, String prefix,def listOfKeys){
if(map instanceof Map){
map.each { key, value->
if(prefix.isEmpty()){
listOfKeys<<key
}else{
listOfKeys<< prefix+"."+key
}
if(value instanceof List){
List list = value
list.eachWithIndex { item, index ->
if(prefix.isEmpty()){
getListOfKeys(item, key+"["+index+"]",listOfKeys)
}else{
getListOfKeys(item, prefix+"."+key+"["+index+"]",listOfKeys)
}
}
}else if(value instanceof Map){
if(prefix.isEmpty()){
getListOfKeys(value, key,listOfKeys)
}else{
getListOfKeys(value, prefix+"."+key,listOfKeys)
}
}
}
}
}
call above method as follows
def void findAllKeysInMap(){
Map map = [ "fields":[
"project":
[
"key": "TP"
],
"summary": "Api Testing Issue.",
"description": "This issue is only for api testing purpose",
"issuetype": [
"name": ["Bug":["hello":[["saurabh":"gaur","om":"prakash"], ["gaurav":"pandey"], ["mukesh":"mishra"]]]]
]
]
]
def listOfKeys=[]
getListOfKeys(map, '', listOfKeys)
println "listOfKeys>>>"+listOfKeys
}
output:
listOfKeys>>>[fields, fields.project, fields.project.key, fields.summary, fields.description, fields.issuetype, fields.issuetype.name, fields.issuetype.name.Bug, fields.issuetype.name.Bug.hello, fields.issuetype.name.Bug.hello[0].saurabh, fields.issuetype.name.Bug.hello[0].om, fields.issuetype.name.Bug.hello[1].gaurav, fields.issuetype.name.Bug.hello[2].mukesh]

There's no such method You're looking for in groovy. You need to do it using instanceof and probably a recursive method.

Slightly shorter:
String key = 'Address.Street'
key.split('\\.').inject(yourMap) {map, path -> map[path]}
If you can't guarantee that the path exists and is complete (say, if you tried to access OrganizationName.id) you'll need to add some safeguards (check that map is not null, and that it's really a Map)

Related

Add new key and value pair under map using groovy

{
"map": {
"key1": [3,12,13,11],
"key2": [21,23],
"key3": [31,32,33]
}}
I have this JSON. similar to key1 or key2 I want to add new key- pair to this json using groovy. I am using JsonSlurper().
def mJson = new File(MAPPINGJSON).text;
def mJsonObject = parser.parseText(mJson);
def list= mJsonObject.map;
def keyFound= false;
for (item in list)
{
if (item.key == templateKey)
{
def values = item.value;
values.add(<some value>);
item.value= values;
keyFound = true;
break;
}
keyFound = false;
}
if(!keyFound)
{
println "Key not found";
// How to add new key pair?
}
list[templateKey] = [<some value>]by daggett is one way, but you can also use a one liner to do the trick.
def list= mJsonObject.map;
list.computeIfAbsent(templateKey, { [] }).add(templateValue)
It uses a function to provide the default value of the map.

Groovy: More elegant way to achieve this?

I have following groovy function:
def getDependantLibs(updatedLib) {
def dependants = []
getAllLibs().each { lib ->
try {
def ref = getFromStash("/repos/${lib}/dependencies.json").find { it.name == updatedLib }
if (ref != null) {
dependants.add([ name: lib, version: ref.version ])
}
} catch (err) {}
}
return dependants
}
My question is, can I achieve this in a more elegant way (maybe with groovy Collecion functions like collect, flatten, ...)
Yes, you can use collectMany. E.g.:
def deps = [1,2,3,4]
println deps.collectMany{
try {
if (it&1) {
throw new RuntimeException(it.toString())
}
(0..it).collect{
[x: it]
}
}
catch (Exception e) {
println "failed: $e.message"
return []
}
}
// output:
// failed: 1
// failed: 3
// [[x:0], [x:1], [x:2], [x:0], [x:1], [x:2], [x:3], [x:4]]
Instead of using Collection.each(Closure closure) you can utilize Collection.collect(Closure transform) for transforming each element of the list from one format to another and Collection.findAll(Closure predicate) to filter null elements from final list. Something like this:
def getDependantLibs(updatedLib) {
return getAllLibs().collect { lib ->
try {
[lib: lib, ref: getFromStash("/repos/${lib}/dependencies.json").find { it.name == updatedLib }]
} catch (err) {
[lib: lib, ref: null]
}
}.findAll { it.ref != null }.collect { [name: it.lib, version: it.ref.version] }
}
Using Java 8 Stream API
You can always use Java 8 Stream API with Groovy. The main advantage of using Stream API in this case is that all operations on stream are lazy - they are executed when reduction function is called. It means that you can apply n-number of transformations and only one iteration will be triggered. In Groovy if you apply let's say 3 bulk collection methods, Groovy will iterate 3 times.
Below you can find an example of using Java 8 Stream API in Groovy:
def getDependantLibsWithStream(updatedLib) {
return getAllLibs().stream()
.map({ lib ->
try {
[lib: lib, ref: getFromStash("/repos/${lib}/dependencies.json").find { it.name == updatedLib }]
} catch (err) {
[lib: lib, ref: null]
}
})
.filter({ it.ref != null })
.map({ [name: it.lib, version: it.ref.version] })
.collect(Collectors.toList())
}
I hope it helps.

Compare all Fields of an Object

In our Integration Tests we wan't to compare every field of an Object returned by an Rest Controller with an object constructed in the test.
This example illustrates the problem:
class RestIntegrationTest extends Specification {
def "Should return contracts"() {
when:
def actual = callRestController()
then:
// compare all fields of actual with "contract"
actual == new Contract(
number: "123",
signDate: "2017-04-01",
address: new Address(
name: "Foobar",
street: "Foostreet",
city: "Frankfurt",
zip: "60486"
),
persons: [new Person(name: "Christian")]
)
}
def callRestController() {
return new Contract(
number: "123",
signDate: "2017-04-01",
address: new Address(
name: "Foobar",
street: "Wrong Street",
city: "Frankfurt",
zip: "60486"
),
persons: [new Person(name: "Frank")]
)
}
static class Contract {
String number
String signDate
Address address
Person[] persons
}
static class Address {
String name
String street
String city
String zip
}
static class Person {
String name
}
}
As output we like expect something like this:
address.street "Wrong Street" != "Foostreet"
persons[0].name "Christian" != "Frank"
Breaking the assert into multiple "==" lines would lead into the correct output, but that will be not handy since some objects are quite huge.
You can try the groovy's #EqualsAndHashCode:
import groovy.transform.EqualsAndHashCode
#EqualsAndHashCode
static class Address {
String name
String street
String city
String zip
}
You can use unitils assertReflectionEquals
http://unitils.sourceforge.net/tutorial-reflectionassert.html
It's not comprehensive but may be sufficient for your needs:
def compareFields( obj1, obj2, propName = null ) {
obj1.properties.each {
if ( it.value instanceof Object[] ) {
def obj2Len = obj2."${it.key}".length
it.value.eachWithIndex { collObj, idx ->
if ( idx + 1 <= obj2Len )
compareFields( collObj, obj2."${it.key}"[idx], "${it.key}[${idx}]" )
}
}
if ( !it.value.class.getCanonicalName().contains( 'java' ) ) {
compareFields( it.value, obj2."${it.key}", it.key )
}
if ( it.value.class.getCanonicalName().contains( 'java' ) &&
it.key != 'class' &&
it.value <=> obj2."${it.key}") {
println "${propName ? "$propName." : ''}${it.key}: '${it.value}' != '" + obj2."${it.key}" + "'"
}
}
}

Conditional Iteration on a parsed json object using each in groovy

I'm trying to create an XML based on the data from a .json. So, my .json file looks something like:
{
"fruit1":
{
"name": "apple",
"quantity": "three",
"taste": "good",
"color": { "walmart": "{{red}}","tj": "{{green}}" }
},
"fruit2":
{
"name": "banana",
"quantity": "five",
"taste": "okay",
"color": { "walmart": "{{gmo}}","tj": "{{organic}}" }
}
}
I can create the XML just fine with the below code, from the above json
import groovy.xml.*
import groovy.json.JsonSlurper
def GenerateXML() {
def jsonSlurper = new JsonSlurper();
def fileReader = new BufferedReader(
new FileReader("/home/workspace/sample.json"))
def parsedData = jsonSlurper.parse(fileReader)
def writer = new FileWriter("sample.XML")
def builder = new StreamingMarkupBuilder()
builder.encoding = 'UTF-8'
writer << builder.bind {
mkp.xmlDeclaration()
"friuts"(version:'$number', application: "FunApp"){
delegate.deployables {
parsedData.each { index, obj ->
"fruit"(name:obj.name, quantity:obj.quantity) {
delegate.taste(obj.taste)
delegate.color {
obj.color.each { name, value ->
it.entry(key:name, value)
}
}
}
}
}
}
}
}
I want to extend this code, such that it looks for particular keys. And if they are present, the loop is performed for those maps as well and as such extends the resulting file.
So, if i have the JSON as like so:
{"fruit1":
{
"name": "apple",
"quantity": "three",
"taste": "good",
"color": { "walmart": "{{red}}","tj": "{{green}}" }
},
"fruit2":
{
"name": "banana",
"quantity": "five",
"taste": "okay",
"color": { "walmart": "{{gmo}}","tj": "{{organic}}" }
},
"chip1":
{
"name": "lays",
"quantity": "one",
"type": "baked"
},
"chip2":
{
"name": "somename",
"quantity": "one",
"type": "fried"
}
}
I want to add an IF, so that it check if any key(s) like 'chip*' is there. And if yes, perform another iteration. If not just skip that section of logic, and not throw any err. like this
import groovy.xml.*
import groovy.json.JsonSlurper
def GenerateXML() {
def jsonSlurper = new JsonSlurper();
def fileReader = new BufferedReader(
new FileReader("/home/okram/workspace/objectsRepo/sample.json"))
def parsedData = jsonSlurper.parse(fileReader)
def writer = new FileWriter("sample.XML")
def builder = new StreamingMarkupBuilder()
builder.encoding = 'UTF-8'
writer << builder.bind {
mkp.xmlDeclaration()
"fruits"(version:'$number', application: "FunApp"){
deployables {
parsedData.each { index, obj ->
"fruit"(name:obj.name, quantity:obj.quantity) {
taste(obj.taste)
color {
obj.color.each { name, value ->
it.entry(key:name, value)
}
}
}
}
}
}
if (parsedData.containsKey('chip*')){
//perform the iteration of the chip* maps
//to access the corresponding values
//below code fails, but that is the intent
parsedData.<onlyTheOnesPassing>.each { index1, obj1 ->
"Chips"(name:obj1.name, quantity:obj1.quantity) {
type(obj1.type)
}
}
}
}
}
I found the same dificult, but on Javascript language, if the logic help you, here what I made:
There are two ways:
You can use the library Lodash on the "get" here: Lodash get or the another one "has": Lodash has.
With they you can put the object and the path and check if there is one without getting any error.
Examples:
_.has(object, 'chip1.name');
// => false
_.has(object, 'fruit1');
// => true
Or you can put the code of the methods here:
// Recursively checks the nested properties of an object and returns the
// object property in case it exists.
static get(obj, key) {
return key.split(".").reduce(function (o, x) {
return (typeof o == "undefined" || o === null) ? o : o[x];
}, obj);
}
// Recursively checks the nested properties of an object and returns
//true in case it exists.
static has(obj, key) {
return key.split(".").every(function (x) {
if (typeof obj != "object" || obj === null || !x in obj)
return false;
obj = obj[x];
return true;
});
}
I hope it helps! :)

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

Resources