I have a map with name/value pairs:
def myMap = [
"username" : "myname",
"age" : 30,
"zipcode" : 10010
]
I have a class called User defined:
class User {
def username;
def age;
def zipcode;
}
I would like to invoke the appropriate setters on the bean. Is there a nice groovy way of doing this? Note, I might have extra stuff in the myMap that wouldn't be set. (The map is a form parameter values map)
I was thinking about using invokeMethod but I was assuming there was a better way.
I found this: Groovy - bind properties from one object to another
And I realized I could do the following:
def prunedMap = [:]
myMap.each{
if (User.metaClass.properties.find{p-> p.name == it.key}) {
prunedMap.put(it.key, it.value)
}
}
User user = new User(prunedMap)
Related
I am using Groovy to create a package that I use in ReadyApi.
In a Groovy script test step, I do the following:
class B {
String value
boolean isSomething
}
class A {
String name
B propB
public A() {
this.name = "Maydan"
}
}
def x = (A) new A().with { propB = new B(value: "Abc", isSomething: true) }
And I get the following error:
org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object 'B#6c218cea' with class 'B' to class 'A'
error at line: 15
Does someone know why? It doesn't make any sense to me.
Kind regards.
PS: I would like to create an instance of class A (by using its parameterless constructor) and setting its field propB in a single statement
You need to return your A object from the .with closure. You may do it like that:
def x = (A) new A().with { propB = new B(value: "Abc", isSomething: true); return it}
but to me personally it looks a little bit odd. I would do it like that:
def x = new A(propB: new B(value: "Abc", isSomething: true))
The same effect, but more compact and readable. It doesn't require to change your A and B definitions, this "map constructor" works out of the box in Groovy, it will call your parameterless constructor and then assigns the necessary fields (propB in your case).
In Groovy When I write the below code in a groovy script.
class Emp {
public String getId() {
return "12345";
}
}
def coercedInstance = [
getId: {
"99999"
}
] as Emp
println new Emp().getId()
println coercedInstance .getId()
Using the as Operator here, am I creating a sub class of the actual Emp class at runtime and providing the method body at run time?
I have seen other stack overflow articles and i have learnt that Groovy uses DefaultGroovyMethods.java & DefaultTypeTransformation.java to do the coercion. But could not figure out if it was subclassing or not.
Yes, an as operator creates an object which type is a subclass of the target class. Using DefaultGroovyMethods.asType(Map map, Class clazz) generates (in a memory) a proxy class that extends given base class.
class Emp {
public String getId() {
return "12345";
}
}
def coercedInstance = [
getId: {
"99999"
}
] as Emp
assert (coercedInstance instanceof Emp)
assert (coercedInstance.class != Emp)
assert (Emp.isAssignableFrom(coercedInstance.class))
println coercedInstance.dump() // <Emp1_groovyProxy#229c6181 $closures$delegate$map=[getId:coercion$_run_closure1#7bd4937b]>
What happens in your case specifically is the following:
The asType method goes to line 11816 to execute ProxyGenerator.INSTANCE.instantiateAggregateFromBaseClass(map, clazz);
In the next step, ProxyGeneratorAdapter object gets created.
In the last step, adapter.proxy(map,constructorArgs) gets called to return a newly generated class that is a proxy of the base class.
It seems that setting a default value for a class property, is not honored by #Builder.
#Test
void test() {
assert Foo.builder().build().getProp() != null // fail
}
#Builder
class Foo {
Map prop = [:]
}
I'll probably fix this by overriding the build method but how?
Not really sure about the implementation of builder() method of #Builder.
I believe you need to initialize the properties / members of the class, then do .build() to create the instance of the class.
Here is the example:
import groovy.transform.builder.Builder
#Builder
class Foo {
Map prop
}
def map = [a:1, b:2]
def f = Foo.builder().prop(map).build()
assert map == f.prop // or you can use f.getProp()
You can quickly try it online Demo
If you notice, the demo example shows how you can initialize multiple properties while building the object.
I wanted to implement functionally, which allows to add unknown properties to the class, during the attempt to set it without using map of dynamic properties.
As Groovy allows to do it using metaClass I used it in propertyMissing method.
class Item {
def propertyMissing(String name, value) {
this.class.metaClass."$name" = value
}
}
But I ran into a weird behavior.
def i1 = new Item()
i1.prop = "value"
println i1.properties // [class:class Item]
println i1.prop // null
i1.metaClass.field = "555"
println i1.properties // [prop:null, class:class Item, field:555]
println i1.prop // null
i1.prop = "value1"
println i1.properties // [prop:value1, class:class Item, field:555]
println i1.prop // value1
Also If I access metaClass before trying to set prop in the example it won't add it anymore
def i1 = new Item()
i1.metaClass.unkn = "1111"
i1.prop = "value"
println i1.properties // [class:class Item, unkn:1111]
println i1.prop // null
i1.metaClass.field = "555"
println i1.properties // [class:class Item, unkn:1111, field:555]
println i1.prop // null
i1.prop = "value1"
println i1.properties // [class:class Item, unkn:1111, field:555]
println i1.prop // null
Why it has such behaviour?
When you update dynamically the metaclass of an object, Groovy replace the metaclass with an ExpandoMetaClass. It's a special implementation of a MetaClass which support adding and removing properties/methods.
However, in your example, Item is a GroovyObject, which have a persistent field on the MetaClass. this field is not updated when the MetaClass is exchanged : Only the metaclass in the registry is replaced by an ExpandoMetaClass. This kind of code can work with a javaobject, because this object doesn't have a field, and the resolution class->metaclass is done every time groovy access the metaclass.
In you know you are going to add properties on a groovy object, you should explicitly set an ExpandoMetaClass :
class Item {
def Item() {
def mc = new ExpandoMetaClass(Item, false, true)
mc.initialize()
this.metaClass = mc
}
def propertyMissing(String name, value) {
this.metaClass."$name" = value
}
}
One of the issues you have is that you're trying to add a property to the class MetaClass instead of the instance MetaClass. And because you're adding the property after creating the instance, the instance doesn't see it. For example, this code fails to print the property:
class A { }
def a = new A()
A.metaClass.prop = 'value'
println a.prop
The error is rather interesting: groovy.lang.MissingPropertyException: No such property: prop for class: A
Possible solutions: prop
However, even if you change the code to use the instance MetaClass it still doesn't work:
class Item {
def propertyMissing(String name, value) {
metaClass."$name" = value
}
}
def i1 = new Item()
i1.prop = 'value'
assert i1.prop == 'value'
The error provides a clue:
groovy.lang.MissingPropertyException: No such property: prop for class: groovy.lang.MetaClassImpl
The MetaClass which provides the Map-like functionality is ExpandoMetaClass. Objects don't typically get this type of MetaClass until you do something like this:
instance.metaClass.prop = 'value'
So the fact that the MetaClass is not an ExpandoMetaClass means that replacement process is not happening. It's likely that propertyMissing() gets called too late in the MOP process to use the MetaClass in this way.
You mentioned that you want to add properties without using a Map of dynamic properties. However, ExpandoMetaClass, which is what you're attempting to use indirectly, uses...
...Maps of dynamic properties! You can see it here.
The easiest way to achieve the behavior you're looking for is to extend Expando:
class Item extends Expando {
def anotherProperty = 'Hello'
}
def i1 = new Item()
i1.prop = 'value'
assert i1.prop == 'value'
assert i1.anotherProperty == 'Hello'
Expando does all of the work for you. If you want to see how it works, read this.
In groovy we can easily create objects from maps and fill the corresponding fields automatically:
def myAddress = new Address([street:"King's street", number:"1200"])
Is it possible to also update an existing object from a map without recreating it? Something like...
myAddress.update([zip: "30555050", city: "London"])
You can use object."${variable}" accessors to do this:
map.each { key, value ->
object."${key}" = value
}
You can then create a method that does this and install that on Object.metaClass and it will be available everywhere:
#Canonical
class MapSet {
String name
int count
static def setAttributesFromMap(Object o, Map<String, Object> map) {
map.each { key, value ->
o."${key}" = value
}
}
static void main(String[] args) {
Object.metaClass.update = {
setAttributesFromMap delegate, it
}
def o = new MapSet([
name: "foo",
count: 5
])
assert o.name == "foo"
assert o.count == 5
o.update([
name: "bar",
count: 6
])
assert o.name == "bar"
assert o.count == 6
}
}
You can use InvokeHelper category and setProperties method, here is a short example:
import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString
import org.codehaus.groovy.runtime.InvokerHelper
#EqualsAndHashCode
#ToString
class Address {
String street
String number
String city
}
Address mainAddress = new Address(street: 'Test', number: '2B', city: 'London')
use InvokerHelper, {
mainAddress.setProperties([street: 'Lorem', number: 'Ipsum'])
}
assert mainAddress.street == 'Lorem'
assert mainAddress.number == 'Ipsum'
assert mainAddress.city == 'London'
Although if you can avoid mutable objects, it's better for you. Otherwise you have to think about thread-safety to do not run into concurrency problems. You can use previous example to create a static method that expects 2 arguments: the existing object and a map of properties to update. In result you get a new instance that contains updated fields. Also you can make your class an immutable one.
After looking up/learning from Szymon's Excellent answer and finding a different way to invoke the helper, it seems like the answer can be simplified to:
InvokerHelper.setProperties(myAddress, [zip: "30555050", city: "London"])"
which is amazingly close to your requested
myAddress.update([zip: "30555050", city: "London"])
I added this as a comment to his question but it's so easy I thought it deserved a terse top-level answer of it's own.