groovy "with" block usage query - groovy

I am trying to use the with block in Groovy to easily initalize my class, but I am getting the following error. Could anyone tell me what I am doing wrong?
MyXMLTemplate template = new MyXMLTemplate ().with {
TxId = 'mnop'
oapTxId = 'abcd'
}
The error I get is:
org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object 'abcd' with class 'java.lang.String' to class 'org.example.MyXMLTemplate'
at org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation.castToType(DefaultTypeTransformation.java:331)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.castToType(ScriptBytecodeAdapter.java:599)
I am using groovy 1.8.0

You need to return the template itself from the with block:
MyXMLTemplate template = new MyXMLTemplate ().with {
TxId = 'mnop'
oapTxId = 'abcd'
it
}

It's hard to see what the problem is without seeing the definition of your class. I'll assume that TxId and oapTxId are both properties of the class.
I suspect your error is caused by oapTxId being of type MyXMLTemplate, and so not assignable from String.
Incidetally, as your with block is just initializing class properties, you could use the more idiomatic constructor and setters approach:
MyXmlTemplate template = new MyXMLTemplate(TxId: 'mnop', oapTxId : 'abcd')

Related

Possibility to disable the property syntax for accessing a getter in groovy?

Let's I have a groovy class like:
class SomeClass {
String myProperty = 'foo'
}
Usually in groovy is will be totally valid to access the value using the property name or the getter - which usually gives the same result for SomeClass:
SomeClass someClass = new SomeClass()
assert someClass.myProperty == 'foo'
assert someClass.getMyProperty() == 'foo'
However - due to a flaw in the Jenkins Pipeline implementation - sometimes(!) you are forced to use the getter - as the plain property access will not work (when using some class hierarchy), see: JENKINS-47143. Bad thing is that the same code may work for some jobs while it doesn't for others:
SomeClass someClass = new SomeClass()
assert someClass.myProperty == 'foo' // sometimes throws 'property not found' error
assert someClass.getMyProperty() == 'foo'
Now I already have couple of unit tests for our Jenkins shared library - but what is missing would be a way to detect a property access, in short: A way to prohibit the property access so the unit tests will already complain in advance.
The following code:
class SomeClass {
String myProperty = 'foo'
}
SomeClass.metaClass.getProperty = { String name ->
throw new RuntimeException("tried to get property ${name}, property access only allowed via getXX() methods")
}
def s = new SomeClass()
println(s.myProperty) // a
println(s.getMyProperty()) // b
will throw an exception for line a but not throw an exception for line b. I suspect this will not be possible if SomeClass was written in java, but assuming a groovy class this could be a way to accomplish what you want.
Running the above will result in:
─➤ groovy solution.groovy
Caught: java.lang.RuntimeException: tried to get property myProperty, property access only allowed via getXX() methods
java.lang.RuntimeException: tried to get property myProperty, property access only allowed via getXX() methods
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
...

Groovy AS keyword for Map > Class binding

Given the following classes:
class A {
B b
int data
int data2
}
class B {
C c
String data
}
class C {
Date data
}
The code works fine:
Date now = new Date()
def a = [ data:42, data2:84, b:[ data:'BBB', c:[ data:now ] ] ] as A
assert a.b.c.data == now
assert a.data == 42
assert a.data2 == 84
Now if I omit the data2:84, the code still works fine except for the last assert of course.
BUT! If I "misspell" the property name like:
def a = [ data:42, data22:84, b:[ data:'BBB', c:[ data:now ] ] ] as A
I'm getting
java.lang.NullPointerException: Cannot get property 'c' on null object
and what I see is that only the A class gets instantiated with a no-arg constructor, and b and c properties are all nulls.
So, skipping != misspelling.
Hence 2 questions:
(rather phylosophycal). is this the expected behaviour? Shouldn't the misspelled props be skipped?
How can the as keyword be made "lenient" to skip the unknown props?
TIA
UPDATE:
A JIRA task is created https://issues.apache.org/jira/browse/GROOVY-9348
There is one main difference. When you use a map constructor that contains existing class fields, a regular A object is initialized. Here is what println a.dump() produces in such a case.
<A#7bab3f1a b=B#1e1a0406 data=42 data2=84>
However, if you put entries to your map that are not represented by the class fields, Groovy does not initialize A object but creates a proxy of A class instead.
<A1_groovyProxy#537f60bf $closures$delegate$map=[data:42, data22:84, b:[data:BBB, c:[data:Fri Dec 20 13:39:50 CET 2019]]] b=null data=0 data2=0>
This proxy does not initialize fields at all, but it stores your map passed with a constructor as an internal $closures$delegate$map field.
Take a look at the following analysis I made using your example.
DefaultGroovyMethods.asType(Map map, Class<?> clazz) throws internally GroovyCastException
org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object '{data=42, data22=84, b={data=BBB, c={data=Fri Dec 20 12:22:28 CET 2019}}}' with class 'java.util.LinkedHashMap' to class 'A' due to: org.codehaus.groovy.runtime.metaclass.MissingPropertyExceptionNoStack: No such property: data22 for class: A
Possible solutions: data2, data
Source: https://github.com/apache/groovy/blob/GROOVY_2_5_8/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java#L11813
The exception is caught and the fallback method is called:
return (T) ProxyGenerator.INSTANCE.instantiateAggregateFromBaseClass(map, clazz);
Source: https://github.com/apache/groovy/blob/GROOVY_2_5_8/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java#L11816
ProxyGenerator initializes ProxyGeneratorAdapter in the following method:
public GroovyObject instantiateAggregate(Map closureMap, List<Class> interfaces, Class clazz, Object[] constructorArgs) {
if (clazz != null && Modifier.isFinal(clazz.getModifiers())) {
throw new GroovyCastException("Cannot coerce a map to class " + clazz.getName() + " because it is a final class");
}
Map<Object,Object> map = closureMap != null ? closureMap : EMPTY_CLOSURE_MAP;
ProxyGeneratorAdapter adapter = createAdapter(map, interfaces, null, clazz);
return adapter.proxy(map, constructorArgs);
}
Source: https://github.com/apache/groovy/blob/GROOVY_2_5_8/src/main/groovy/groovy/util/ProxyGenerator.java#L162
This method has potentially a bug: in the createAdapter(map, interfaces, null, clazz) part, the null value represents delegatingClass object. When it is null, there is no delegation mechanism applied in the generated proxy class.
Last but not least, adapter.proxy(map, constructorArgs) instantiates a new object, which string dump representation looks like this:
<A1_groovyProxy#537f60bf $closures$delegate$map=[data:42, data22:84, b:[data:BBB, c:[data:Fri Dec 20 13:29:06 CET 2019]]] b=null data=0 data2=0>
As you can see, the map passed to the constructor is stored as $closure$delegate$map field. All A class values are initialized with their default values (null for b field, and 0 for remaining int fields.)
Now, ProxyGenerator class has a method that creates an adapter that supports delegation:
public GroovyObject instantiateDelegateWithBaseClass(Map closureMap, List<Class> interfaces, Object delegate, Class baseClass, String name) {
Map<Object,Object> map = closureMap != null ? closureMap : EMPTY_CLOSURE_MAP;
ProxyGeneratorAdapter adapter = createAdapter(map, interfaces, delegate.getClass(), baseClass);
return adapter.delegatingProxy(delegate, map, (Object[])null);
}
Source: https://github.com/apache/groovy/blob/GROOVY_2_5_8/src/main/groovy/groovy/util/ProxyGenerator.java#L203
I'm guessing that maybe if the ProxyGeneratorAdapter with a non-null delegatingClass was used, calling a.b would use a value from the internal delegate map, instead of the b field value. That's only my assumption.
The question is: is this a bug? It depends. As cfrick mentioned in one of the comments, initializing A with an incorrect map throws an explicit error, and you are done. Here this exception is suppressed and from the caller perspective, you have no idea what happened in the background. I run those tests using Groovy 2.5.8 and 3.0.0-RC1, the same behavior in both versions. Reporting this is an issue in the Apache Groovy JIRA project sounds reasonable, so you can get feedback from the Groovy core maintainers.

Add strong typing to objects from JsonSlurper

I'm having some trouble getting typing to work with the JsonSlurper in Groovy. I'm fairly new to Groovy, and even newer to adding strong types to it - bear with me.
Right now I've created a trait which defines the general shape of my JSON object, and I'm trying to cast the results of parseText to it.
import groovy.json.JsonSlurper
trait Person {
String firstname
String lastname
}
def person = (Person)(new JsonSlurper().parseText('{"firstname": "Lando", "lastname": "Calrissian"}'))
println person.lastname
This throws
Exception in thread "main" org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object '{firstname=Lando, lastname=Calrissian}' with class 'org.apache.groovy.json.internal.LazyMap' to class 'Person' due to: groovy.lang.GroovyRuntimeException: Could not find matching constructor for: Person(org.apache.groovy.json.internal.LazyMap)
...
I can see why my code doesn't make sense, I'm not trying to change the type of the data (casting), I'm just trying to let my IDE know that this is what's inside of my object.
Is it possible to at least add code completion to my JSON objects? I'd love to get runtime type checking, as well, but it's not necessary.
you could try to use delegate
this allows to wrap class around map
import groovy.json.JsonSlurper
class Person {
#Delegate Map delegate
String getFirstname(){ delegate.get('firstname') }
String getLastname(){ delegate.get('lastname') }
}
def person = new Person(delegate:new JsonSlurper().parseText('{"firstname": "Lando", "lastname": "Calrissian"}'))
println person.lastname
or for example use Gson for parsing:
#Grab(group='com.google.code.gson', module='gson', version='2.8.5')
import com.google.gson.Gson
class Person {
String firstname
String lastname
}
def person = new Gson().fromJson('{"firstname": "Lando", "lastname": "Calrissian"}', Person.class)
assert person instanceof Person
println person.lastname
This actually is a cast and Groovy will try to turn your Map into said object.
From the docs:
The coercion operator (as) is a variant of casting. Coercion converts object from one type to another without them being compatible for assignment.
The way this works for a POJO is to construct a new object using the Map-c'tor. This will either unroll into calling setters or works directly with static compilation.
Be aware, that using maps with excess keys will lead to errors. So I'd only use this for toy projects. Use a proper JSON-mapper like e.g. Jackson instead.
So the solution here is to not use a trait (which is basically a interface) but a regular class.

Micronaut CompileStatic JSON object -Static type checking- No such property: bookid for class: java.lang.Object

In my Micronaut Controller I have below code to parse the JSON object. when I use #CompileStatic annotation it throwing this below error.
#Post("/save")
def save(#Body Object JSON) {
String bookid=JSON?.bookid
String name=JSON?.name
def b =bookService.save(bookid,name)
return HttpResponse.created(b)
}
Error
BookController.groovy: 58: [Static type checking] - No such property: bookid for class: java.lang.Object
Is there way to fix this error message with compilestatic annotation?
Thanks
SR
With Help of Jeff Brown I have changed. my save method like this.
#Post('/')
Book save(Book b) {
bookService.save b
}
Micronaut JSON post strip the Qutoes
You can also work with your method instead of changing it for parsing.I encountered the same problem and the method that worked for me is using String instead of object. Just use JSON String along with #BODY and then parse it using ObjectMapper().
here is the answer i posted at some other question, hope it will help you out.
https://stackoverflow.com/a/54905403/7803105

Trivial deserialization failing with YamlDotNet

What can possible go wrong with this:
public void Main()
{
var input = new StringReader(Document);
var deserializer = new Deserializer(namingConvention: new CamelCaseNamingConvention());
var p = deserializer.Deserialize<Person>(input);
Console.WriteLine(p.Name);
}
public class Person
{
public string Name {get;set;}
}
private const string Document = #"Name: Peter";
A serialization exception is thrown:
Property 'Name' not found on type 'YamlDotNet.Samples.DeserializeObjectGraph+Person'
The same happens if I first serialize a Person object using the Serializer.
While the online sample for deserialization works just fine - this trivial code does not. What am I missing? It must be a stupid little detail. (But it happened before with other data structures I tried.)
As it seems, the problem is with the namingConvention parameter. If I don't set it to an instance of CamelCaseNamingConvention all is fine.
Unfortunately the "canonical" example (https://dotnetfiddle.net/HD2JXM) uses it and thus suggests it is important.
For any reason the CamelCaseNamingConvention converts the fields to lowercase in the class (ie. 'Name' to 'name'). As the string is 'Name' and not 'name' the deserialization fails. The example uses lower-case therefore it works....
I had the same problem....

Resources