How to get a value of a dynamic key in Groovy JSONSlurper? - groovy

The variable resp contains below JSON response -
{"name":"sample","address":{"country":"IN","state":"TN","city":"Chennai"}}
I have planned using param1 variable to get the required key from JSON response, but I'm unable to get my expected results.
I'm passing the param1 field like - address.state
def actValToGet(param1){
JsonSlurper slurper = new JsonSlurper();
def values = slurper.parseText(resp)
return values.param1 //values.address.state
}
I'm getting NULL value here -> values.param1
Can anyone please help me. I'm new to Groovy.

The map returned from the JsonSlurper is nested rather than than flat. In other words, it is a map of maps (exactly mirroring the Json text which was parsed). The keys in the first map are name and address. The value of name is a String; the value of address is another map, with three more keys.
In order to parse out the value of a nested key, you must iterate through each layer. Here is a procedural solution to show what's happening.
class Main {
static void main(String... args) {
def resp = '{"name":"sample","address":{"country":"IN","state":"TN","city":"Chennai"}}'
println actValToGet(resp, 'address.state')
}
static actValToGet(String resp, String params){
JsonSlurper slurper = new JsonSlurper()
def values = slurper.parseText(resp)
def keys = params.split(/\./)
def output = values
keys.each { output = output.get(it) }
return output
}
}
A more functional approach might replace the mutable output variable with the inject() method.
static actValToGet2(String resp, String params){
JsonSlurper slurper = new JsonSlurper()
def values = slurper.parseText(resp)
def keys = params.split(/\./)
return keys.inject(values) { map, key -> map.get(key) }
}
And just to prove how concise Groovy can be, we can do it all in one line.
static actValToGet3(String resp, String params){
params.split(/\./).inject(new JsonSlurper().parseText(resp)) { map, key -> map[key] }
}
You may want to set a debug point on the values output by the parseText() method to understand what it's returning.

Related

How do I define an entire object and methods on single line

This throws an error (can't set on null object)
def currentBuild = [:].rawBuild.getCauses = { return 'hudson.model.Cause$UserIdCause#123abc' }
I need to do it on multiple lines like this
def currentBuild = [:]
currentBuild.rawBuild = [:]
currentBuild.rawBuild.getCauses = { return 'hudson.model.Cause$UserIdCause#123abc' }
Is there a terse way to define this object on a single line or statement? I don't understand why my single line attempt doesn't work.
Instead of chaining setters, I'd just use a map literal with the nested
values. E.g.
def currentBuild = [rawBuild: [getCauses: { return 'hudson.model.Cause$UserIdCause#123abc' }]]
println currentBuild.rawBuild.getCauses()
// → hudson.model.Cause$UserIdCause#123abc
If you have to go more imperative instead of declarative, have a look at
.get(key, fallback), .withDefault{ ... }, .tap{ ... }.
BTW: those are not objects but just maps.

Groovy MarkupBuilder : How to create markup and append string

I am using the Groovy MarkupBuilder to create some XML.
In my scenario I get an xml string which is essentially the body of the xml document and then I want to surround it with some other stuff.
Something like this....
def xmltext = '''<node><name short="yes">tim</name><fun>maybe</fun></node>'''
def body = new XmlSlurper(false,false).parseText( xmltext )
def writer = new StringWriter()
def xml = new MarkupBuilder(writer)
xml.mkp.xmlDeclaration version: '1.0', encoding: 'UTF-8'
xml.'cre:InputParamters'('xmlns:cre': 'http://xmlns.oracle.com/apps/ar/soaprovider/plsql/ar_invoice_api_pub/create_single_invoice/') {
xml.'cre:P_P_TRX_HEADER_TBL' {
xml.'cre:P_TRX_HEADER_TBL_ITEM' {
map 'cre:TRX_HEADER_ID', '',xml
map 'cre:TRX_DATE', requestPayload?.invoiceDate, xml
map 'cre:TRX_CURRENCY', requestPayload?.currencyCode, xml
map 'cre:TRX_CLASS', 'INV', xml
map 'cre:CUST_TRX_TYPE_ID', '1034', xml
map 'cre:BILL_TO_CUSTOMER_ID', '147055', xml
}
}
<<APPEND ELEMENTS FROM XML STRING HERE>>
}
private map (Object field, Object value, MarkupBuilder xml) {
if (value) {
xml."$field"(value)
}
}
return writer.toString()
Could someone help me with the bit 'APPEND ELEMENTS FROM XML STRING HERE' and how to get that the work for me?
thanks
Here is the changed script:
Note that you have used some variable to get the values. To demonstrate I have used fixed values below which you may be able to replace it.
import groovy.xml.*
def xmltext = '''<node><name short="yes">tim</name><fun>maybe</fun></node>'''
def builder = new StreamingMarkupBuilder()
builder.encoding = 'UTF-8'
def xml = builder.bind {
mkp.xmlDeclaration()
namespaces << [cre:'http://xmlns.oracle.com/apps/ar/soaprovider/plsql/ar_invoice_api_pub/create_single_invoice/']
cre.InputParamters{
cre.P_P_TRX_HEADER_TBL {
cre.P_TRX_HEADER_TBL_ITEM {
cre.TRX_HEADER_ID ('')
cre.TRX_DATE ('2017-02-17')
cre.TRX_CURRENCY( 'USD')
cre.TRX_CLASS('INV')
cre.CUST_TRX_TYPE_ID( '1034')
cre.BILL_TO_CUSTOMER_ID( '147055')
}
}
mkp.yieldUnescaped xmltext
}
}
println XmlUtil.serialize(xml)
You may try it quickly online Demo
Output

How to convert list of maps to list of objects if list of maps has extra keys

My question is almost similar to How to convert list of maps to list of objects.
But the problem is now, I have list of maps where maps contains some extra keys which is not present in my Pojo class as property like as below :-
List list = [
[param1: "a", param2: ["a","b","c"], param3:[a:"a",b:"b",c:"c"], param4:true, param5:1, param6: "pamra6",param7: "pamra7"],
[param1: "b", param2: ["d","e","f"], param3:[d:"d",e:"e",f:"f"], param4:false, param5:2, param6: "pamra6",param7: "pamra7"]
]
In this list two extra keys param6, param7 included where this is not exist in Pojo class, because in my scenario I'm considering only those property which present in the Pojo, I can't increase extra property in the Pojo class.
So when I'm going to convert this list of maps to list of objects as below :-
list.collect { new Pojo(it) }
it's throwing an error as :-
groovy.lang.MissingPropertyException: No such property: param6 for
class: Pojo
Which is absolutely correct, but when I'm converting like below :-
list.collect { it as Pojo }
or
list*.asType(Pojo)
It's not throwing any error but when we going to get values like this :-
.each { pojo ->
pojo.param1
------------
------------
}
Couldn't found any of these value. all values found as null.
When I'm examine converted list of objects using .dump(), it converted as Pojo1_groovyProxy like as proxy object..
So my question is, how to convert in this situation???
Here is one way to do it (which attempts to be resilient if param6 were later added to the class):
(edit: much cleaner, thanks to tim_yates)
class Pojo {
def param1
def param2
def param3
def param4
def param5
def static build(def map) {
def fields = Pojo.declaredFields.findAll { !it.synthetic }*.name
def keys = map.keySet().findAll { it in fields }
def subMap = map.subMap(keys)
new Pojo(subMap)
}
}
def newList = list.collect { Pojo.build(it) }
After Michael Easter and tim_yates suggestion, achieved this using as below :-
class Pojo {
def param1
def param2
def param3
def param4
def param5
def static build(def map) {
new Pojo(map.findAll { k, v -> k in Pojo.metaClass.properties*.name})
}
}
def newList = list.collect { Pojo.build(it) }
Thanks for giving me big hint..:)

XML Slurper - empty string for attributes

When parsing an attribute, the slurper sets an empty string when an attribute is not found.
For e.g., car.setOwner(node.#owner.text());
In the above code, if the owner attribute is not found, then the slurper sets a blank string ("").
In my case, I'd rather leave it as null than setting an empty string.
Is it possible to configure the Slurper not to do this?
You could do
car.setOwner(node.#owner.text() ?: null)
If we distinguish between configure and using the Meta Object Protocol (MOP), then we can state that it is not possible to configure XmlSlurper as you describe, but it is possible to use the MOP.
For configure, note the following:
def node = new XmlSlurper().parseText('<car>No Owner</car>' )
def attr = node.#owner
assert groovy.util.slurpersupport.Attributes == attr.getClass()
If you look at the code for Attributes.text() (in this case, Groovy 2.2.2), it is clear that this cannot be configured to return null.
For MOP, we can capture the original Attributes.text() method and then override it:
import groovy.util.slurpersupport.*
def originalText = Attributes.metaClass.getMetaMethod("text")
Attributes.metaClass.text = { ->
def result = originalText.invoke(delegate)
if (result.isEmpty()) {
result = null
}
result
}
// no owner
def node = new XmlSlurper().parseText('<car>No Owner</car>')
def attr = node.#owner
assert null == attr.text()
// with owner
node = new XmlSlurper().parseText('<car owner="codetojoy"></car>')
attr = node.#owner
assert "codetojoy" == attr.text()

Map with default value created in a closure

I want to store objects in a map (called result). The objects are created or updated from SQL rows.
For each row I read I access the map as follows:
def result = [:]
sql.eachRow('SELECT something') { row->
{
// check if the Entry is already existing
def theEntry = result[row.KEY]
if (theEntry == null) {
// create the entry
theEntry = new Entry(row.VALUE1, row.VALUE2)
// put the entry in the result map
result[row.KEY] = theEntry
}
// use the Entry (create or update the next hierarchie elements)
}
I want to minimize the code for checking and updating the map. How can this be done?
I know the function map.get(key, defaultValue), but I will not use it, because it is to expensive to create an instance on each iteration even if I don't need it.
What I would like to have is a get function with a closure for providing the default value. In this case I would have lazy evaluation.
Update
The solution dmahapatro provided is exactly what I want. Following an example of the usage.
// simulate the result from the select
def select = [[a:1, b:2, c:3], [a:1, b:5, c:6], [a:2, b:2, c:4], [a:2, b:3, c:5]]
// a sample class for building an object hierarchie
class Master {
int a
List<Detail> subs = []
String toString() { "Master(a:$a, subs:$subs)" }
}
// a sample class for building an object hierarchie
class Detail {
int b
int c
String toString() { "Detail(b:$b, c:$c)" }
}
// the goal is to build a tree from the SQL result with Master and Detail entries
// and store it in this map
def result = [:]
// iterate over the select, row is visible inside the closure
select.each { row ->
// provide a wrapper with a default value in a closure and get the key
// if it is not available then the closure is executed to create the object
// and put it in the result map -> much compacter than in my question
def theResult = result.withDefault {
new Master(a: row.a)
}.get(row.a)
// process the further columns
theResult.subs.add new Detail(b: row.b, c: row.c )
}
// result should be [
// 1:Master(a:1, subs:[Detail(b:2, c:3), Detail(b:5, c:6)]),
// 2:Master(a:2, subs:[Detail(b:2, c:4), Detail(b:3, c:5)])]
println result
What I learned from this sample:
withDefault returns a wrapper, so for manipulating the map use the wrapper and not the original map
row variable is visible in the closure!
create the wrapper for the map in each iteration again, since row var changed
You asked for it, Groovy has it for you. :)
def map = [:]
def decoratedMap = map.withDefault{
new Entry()
}
It works the same way you would expect it to work lazily. Have a look at withDefault API for a detailed explanation.

Resources