the "groovy" way of accessing nested fields - groovy

take these objects
class Obj1 {
Obj2 obj2
}
class Obj2 {
Obj3 obj3
}
class Obj3 {
String tryme
}
Now, Crud operations on this model is happening by means of an angularjs app. The angular app sends back the fields that changed. so for example, it may send
[
{
"jsonPath": "/obj2/obj3/tryme",
"newValue": "New Name"
}
]
So with groovy, is there an easy way to access that nested field? i could do it with java reflection, but thats a lot of code. If not with pojo's, this is a mongodb, so I suppose i can do it with json slurp if its easier, i just don't know. any advice is appreciated.
So to show the problems with the solutions i have found so far. Take this
Obj1 a = new Obj1​()
with the edit object of this
[
{
"jsonPath": "/obj2/obj3/tryme",
"newValue": "New Name"
}
]
Doing the pojo route, finding a null field of obj2 is not an issue. The issue is i have no way of knowing what type it is in order to initialize the field and keep walking the tree.
Please refrain from Groovy is typeless, we don't use def around here, everything needs to be statically typed.
So I am also trying this from the JsonSlurp aspect too, just eliminate the pojo all together. But even that is problematic because it seems I'm back to iterating a map of maps to get to the field. Same problem, easier to solve.
class MongoRecordEditor {
def getProperty(def object, String propertyPath) {
propertyPath.tokenize('/').inject object, {obj, prop ->
def retObj = obj[prop]
if (retObj == null){
println obj[prop].class
}
}
}
void setProperty(def object, String propertyPath, Object value) {
def pathElements = propertyPath.tokenize('/')
def objectField
if (pathElements.size() == 1){
objectField = pathElements[0]
} else {
objectField = pathElements[0..-2].join('/')
}
Object parent = getProperty(object, objectField)
parent[pathElements[-1]] = value
}
}
is the culmination of many ideas. Now getting def retObj = obj[prop] to run is a piece of cake. the problem is, if the field isn't initialized, then retObj is always null, therefore i can't get the type that its supposed to be to initialize it.
and yes I know, once I figure out how to make it work, I will type it.

Maybe something like this?
class Obj1 {
Obj2 obj2
}
class Obj2 {
Obj3 obj3
}
class Obj3 {
String tryme
}
def a = new Obj1​(obj2: new Obj2(obj3: new Obj3(tryme:"test")))
for (value in "obj2/obj3/tryme".split("/")) {
a = a?."${value}"
}
println a
You could create trait and then make Obj1 use it:
trait DynamicPath {
def get(String path) {
def target = this
for (value in path.split("/")) {
target = target?."${value}"
}
target
}
}
​class Obj1 implements DynamicPath{
Obj2 obj2
}
println a.get("obj2/obj3/tryme");​

Not sure if this is what you want... And it relies on the objects having a default constructor, and there may be better ways of doing it...
Those caveats aside, given you have:
import groovy.transform.*
import groovy.json.*
#ToString
class Obj1 {
Obj2 obj2
}
#ToString
class Obj2 {
Obj3 obj3
}
#ToString
class Obj3 {
String tryme
}
def changeRequest = '''[
{
"jsonPath": "/obj2/obj3/tryme",
"newValue": "New Name"
}
]'''
Then, you can define a manipulator like so:
def change(Object o, String path, String value) {
Object current = o
String[] pathElements = path.split('/').drop(1)
pathElements[0..-2].each { f ->
if(current."$f" == null) {
current."$f" = current.class.declaredFields.find { it -> f == it.name }?.type.getConstructor().newInstance()
}
current = current."$f"
}
current."${pathElements[-1]}" = value
o
}
And call it like
def results = new JsonSlurper().parseText(changeRequest).collect {
change(new Obj1(), it.jsonPath, it.newValue)
}
To give you a list containing your one new Obj1 instance:
[Obj1(Obj2(Obj3(New Name)))]

Related

How can I create a map from object properties?

I get an object via some 3rd party api. I use a wrapper function to get it and then return a map from its properties:
wrapperFunc() {
def myObj = someapi.getblah().getSomeObect()
return [
aaa: myObj.aaa,
bbb: myObj.bbb,
ccc: myObj.ccc
]
}
Now I could manually go through EVERY property in the object like this, but is there an elegant groovy feature to dynamically build a map from the object's properties?
You could do something like this:
class Widget {
int width
int height
static void main(args) {
def obj = new Widget(width: 7, height: 9)
List<MetaProperty> metaProperties = obj.metaClass.properties
def props = [:]
for(MetaProperty mp : metaProperties) {
props[mp.name] = mp.getProperty(obj)
}
// props will look like [width:7, class:class demo.Widget, height:9]
}
}
This is basically a variant of #jeff-scott-brown's answer.
First, create a class that contains the Object-to-Map logic that uses the Groovy MetaClass to access a type's properties. findAll filters out the "class" property, which I assume you don't care about. The collectEntries line transforms each MetaProperty object into a Map entry.
class ElegantGroovyFeature {
static Map asType(Object o, Class m) {
if (m == Map) {
o.metaClass.properties
.findAll { it.getSetter() != null }
.collectEntries { prop -> [prop.name, prop.getProperty(o)] }
} else {
o.asType(m)
}
}
}
The extension class overrides the asType method, which corresponds to the as operator, enabling you to convert arbitrary objects to Maps using obj as Map expressions:
def obj = someapi.getBlah().getSomeObject()
use (ElegantGroovyFeature) {
def mapOfProperties = obj as Map
}
const obj = { foo: 'bar', baz: 42 };
const map = new Map(Object.entries(obj));
console.log(map); // Map { foo: "bar", baenter code herez: 42 }

Clone of list still correct the original list

In groovy the original value get overwritten when I change values in a clone list. Does anyone know if I am doing it wrong or it is a bug older groovy?
I am doing something like this:
List<Foo> myFooList = fooList.newFoos.findAll { it.type == "Types}
List<Foo> newFoo = fooList.oldFoos.findAll { it.type == "Types}.clone()
newFoo.each {
it.value = "neeeew value"
}
Foo fooOne = newFoo.each { foooo ->
fooTwo = fooList.oldFoos.find { it.id == foooo.id}
if(fooTwo.value != foooo.value) {
//Here it should go... but it turns out that fooTwo.value == foooo.value
}
}
the clone method called on list produces a new list but with the same objects in it.
you want to build new list with new objects. here is an example:
#groovy.transform.ToString
class Foo{
String type
String value
}
def fooList = [
new Foo(type:"Types", value:'old value1'),
new Foo(type:"Not", value:'old value2'),
new Foo(type:"Types", value:'old value3'),
new Foo(type:"Not", value:'old value4'),
]
def newFooList = fooList.
findAll{it.type=='Types'}.
collect{ new Foo(type:it.type, value:"new value") } //build new array with transformed elements
//check the original list
fooList.each{assert it.value!='new value'}
//check new list
newFooList.each{assert it.value=='new value'}
assert newFooList.size()==2
println fooList
println newFooList
I solved the issue by adding clone of the element as well, any way it became to much of cowboy fix:
List<Foo> myFooList = fooList.newFoos.findAll { it.type == "Types}
List<Foo> newFoo = fooList.oldFoos.findAll { it.type == "Types}.collect {it.clone()}
newFoo.each {
it.value = "neeeew value"
}
Foo fooOne = newFoo.each { foooo ->
fooTwo = fooList.oldFoos.find { it.id == foooo.id}
if(fooTwo.value != foooo.value) {
//Here it should go... but it turns out that fooTwo.value == foooo.value
}
}

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.

How to do Groovy method signatures with Python-style kwargs AND default values?

I might be asking too much, but Groovy seems super flexible, so here goes...
I would like a method in a class to be defined like so:
class Foo {
Boolean y = SomeOtherClass.DEFAULT_Y
Boolean z = SomeOtherClass.DEFAULT_Z
void bar(String x = SomeOtherClass.DEFAULT_X,
Integer y = this.y, Boolean z = this.z) {
// ...
}
}
And to be able to provide only certain arguments like so:
def f = new Foo(y: 16)
f.bar(z: true) // <-- This line throws groovy.lang.MissingMethodException!
I am trying to provide an API that is both flexible and type safe, which is the problem. The given code is not flexible in that I would have to pass in (and know as the user of the API) the default value for x in order to call the method. Here are some challenges for the solution I want:
Type safety is a must--no void bar(Map) signatures unless the keys can somehow be made type safe. I realize with this I could do the type checking in the method body, but I'm trying to avoid that level of redundancy as I have many of this "kind" of method to write.
I could use a class for each method signature--something like:
class BarArgs {
String x = SomeOtherClass.DEFAULT_X
String y
String z
}
And define it like:
void bar(BarArgs barArgs) {
// ...
}
And call it using my desired way using the map constructor: f.bar(z: true), but my problem lies in the object's default on y. There's no way to handle that (that I know of) without having to specify it when calling the method as in: f.bar(y: f.y, z: true). This is fine for my little sample, but I'm looking at 20-30 optional parameters on some methods.
Any suggestions (or questions if needed) are welcome! Thank you for taking a look.
Interesting question. I've interpreted your requirements like this
The class should have a set of default properties.
Each method should have a set of default arguments.
The method defaults override the class defaults.
Each method can have additional arguments, not existing on the class.
The method arguments should not modify the class instance.
Provided arguments needs to be checked for type.
I was not sure about number 5 since it is not explicitly specified, but it
looked like that was what you wanted.
As far as I know, there is nothing built-in in groovy to support all this,
but there are several ways to make it work in a "simple-to-use" manner.
One way that comes to mind is to create specialized argument classes, but
only use maps as the arguments in the methods. With a simple super-class
or trait to verify and set the properties, it is a one-liner to get the
actual arguments for each method.
Here is a trait and some examples that can be used as a starting point:
trait DefaultArgs {
void setArgs(Map args, DefaultArgs defaultArgs) {
if (defaultArgs) {
setArgs(defaultArgs.toArgsMap())
}
setArgs(args)
}
void setArgs(Map args) {
MetaClass thisMetaClass = getMetaClass()
args.each { name, value ->
assert name instanceof String
MetaProperty metaProperty = thisMetaClass.getMetaProperty(name)
assert name && metaProperty != null
if (value != null) {
assert metaProperty.type.isAssignableFrom(value.class)
}
thisMetaClass.setProperty(this, name, value)
}
}
Map toArgsMap() {
def properties = getProperties()
properties.remove('class')
return properties
}
}
With this trait is it easy to create specialized argument classes.
#ToString(includePackage = false, includeNames = true)
class FooArgs implements DefaultArgs {
String a = 'a'
Boolean b = true
Integer i = 42
FooArgs(Map args = [:], DefaultArgs defaultArgs = null) {
setArgs(args, defaultArgs)
}
}
#ToString(includePackage = false, includeNames = true, includeSuper = true)
class BarArgs extends FooArgs {
Long l = 10
BarArgs(Map args = [:], FooArgs defaultArgs = null) {
setArgs(args, defaultArgs)
}
}
And a class that uses these arguments:
class Foo {
FooArgs defaultArgs
Foo(Map args = [:]) {
defaultArgs = new FooArgs(args)
}
void foo(Map args = [:]) {
FooArgs fooArgs = new FooArgs(args, defaultArgs)
println fooArgs
}
void bar(Map args = [:]) {
BarArgs barArgs = new BarArgs(args, defaultArgs)
println barArgs
}
}
Finally, a simple test script; output of method invocations in comments
def foo = new Foo()
foo.foo() // FooArgs(a:a, b:true, i:42)
foo.foo(a:'A') // FooArgs(a:A, b:true, i:42)
foo.bar() // BarArgs(l:10, super:FooArgs(a:a, b:true, i:42))
foo.bar(i:1000, a:'H') // BarArgs(l:10, super:FooArgs(a:H, b:true, i:1000))
foo.bar(l:50L) // BarArgs(l:50, super:FooArgs(a:a, b:true, i:42))
def foo2 = new Foo(i:16)
foo2.foo() // FooArgs(a:a, b:true, i:16)
foo2.foo(a:'A') // FooArgs(a:A, b:true, i:16)
foo2.bar() // BarArgs(l:10, super:FooArgs(a:a, b:true, i:16))
foo2.bar(i:1000, a:'H') // BarArgs(l:10, super:FooArgs(a:H, b:true, i:1000))
foo2.bar(l:50L) // BarArgs(l:50, super:FooArgs(a:a, b:true, i:16))
def verifyError(Class thrownClass, Closure closure) {
try {
closure()
assert "Expected thrown: $thrownClass" && false
} catch (Throwable e) {
assert e.class == thrownClass
}
}
// Test exceptions on wrong type
verifyError(PowerAssertionError) { foo.foo(a:5) }
verifyError(PowerAssertionError) { foo.foo(b:'true') }
verifyError(PowerAssertionError) { foo.bar(i:10L) } // long instead of integer
verifyError(PowerAssertionError) { foo.bar(l:10) } // integer instead of long
// Test exceptions on missing properties
verifyError(PowerAssertionError) { foo.foo(nonExisting: 'hello') }
verifyError(PowerAssertionError) { foo.bar(nonExisting: 'hello') }
verifyError(PowerAssertionError) { foo.foo(l: 50L) } // 'l' does not exist on foo

using gstring to access list property element

Using GStrings one can access the properties of the object, including nested properties. But how to access the n'th element inside a list property?
class Foo {
List<Bar> elements
}
class Bar {
String version
}
I need to access version property in Foo.elements object for a specific index using GString.
Tried below code without success.
def property = "elements[0].version"
fooObject."$property" fails to identify the property
So there are three ways in which I think this problem can be solved depending upon how much flexibility is allowed
class Foo {
List<Bar> elements
}
class Bar {
String version
}
Let's say fooObject is the object of Foo, e.g.:
def fooObject = new Foo(elements:[new Bar(version:1), new Bar(version:2)])
If this is possible for you:
println fooObject."elements"[1]."version"
Otherwise, put everything in a string and then interpolate:
println "${fooObject.elements[1].version}"
Ultimately, if both of the above don't fly for you:
def property='elements[1].version'
def expr = 'fooObject.' + property
println Eval.me('fooObject', fooObject, expr)
The last one makes the fooObject available as fooObject to the expression being evaluated and evaluates the expression.
Ideally, it could be:
def prop1 = "elements"
def prop2 = "version"
fooObject."$prop1"[0]."$prop2"
Lengthy and generic one would be using inject:
class Foo {
List<Bar> elements
}
class Bar {
String version
}
def fooObject = new Foo(elements: [new Bar(version: '1'),
new Bar(version: '2'),
new Bar(version: '3')])
def fetchVersion(property, fooObject) {
property.tokenize(/./).inject(fooObject) {obj, elem ->
if(elem.contains(/[/)){
def var = elem.tokenize(/[]/)
obj?."${var[0]}".getAt("${var[1]}".toInteger())
} else {
obj?."$elem"
}
}
}
assert fetchVersion("elements[0].version", fooObject) == '1'
assert fetchVersion("elements[1].version", fooObject) == '2'
assert fetchVersion("elements[2].version", fooObject) == '3'
assert fetchVersion("elements[7].version", fooObject) == null

Resources