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 }
Related
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.
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)))]
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
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
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