11

I'm wondering what is the best way to retrieve nested properties in Groovy, taking a given Object and arbitrary "property" String. I would like to something like this:

someGroovyObject.getProperty("property1.property2")

I've had a hard time finding an example of others wanting to do this, so maybe I'm not understanding some basic Groovy concept. It seems like there must be some elegant way to do this.

As reference, there is a feature in Wicket that is exactly what I'm looking for, called the PropertyResolver: http://wicket.apache.org/apidocs/1.4/org/apache/wicket/util/lang/PropertyResolver.html

Any hints would be appreciated!

Nathan Beach
  • 2,497
  • 2
  • 24
  • 25

4 Answers4

25

I don't know if Groovy has a built-in way to do this, but here are 2 solutions. Run this code in the Groovy Console to test it.

def getProperty(object, String property) {

  property.tokenize('.').inject object, {obj, prop ->       
    obj[prop]
  }  
}

// Define some classes to use in the test
class Name {
  String first
  String second
}

class Person {
  Name name
}

// Create an object to use in the test
Person person = new Person(name: new Name(first: 'Joe', second: 'Bloggs'))

// Run the test
assert 'Joe' == getProperty(person, 'name.first')

/////////////////////////////////////////
// Alternative Implementation
/////////////////////////////////////////
def evalProperty(object, String property) {
  Eval.x(object, 'x.' + property)
}

// Test the alternative implementation
assert 'Bloggs' == evalProperty(person, 'name.second')
Dónal
  • 185,044
  • 174
  • 569
  • 824
  • This is more or less what I'm doing now, but your way is cleaner! – Nathan Beach Mar 30 '11 at 17:03
  • sorry, Don, should have accepted your answer long ago (back when I didn't know what I was doing on this site). thanks... – Nathan Beach Mar 26 '13 at 16:33
  • Note: I tested these in a sort, and Eval was much slower than the tokenized version on my system. – Ed J May 24 '17 at 00:23
  • I would suggest using a null check in the getProperty implementation. Otherwise, it will throw an exception on nulls when traversing your object graph. (Submitted edit) – Ed J May 24 '17 at 00:27
3

Groovy Beans let you access fields directly. You do not have to define getter/setter methods. They get generated for you. Whenever you access a bean property the getter/setter method is called internally. You can bypass this behavior by using the .@ operator. See the following example:

class Person {
    String name
    Address address
    List<Account> accounts = []
}

class Address {
    String street
    Integer zip
}

class Account {
    String bankName
    Long balance
}

def person = new Person(name: 'Richardson Heights', address: new Address(street: 'Baker Street', zip: 22222)) 
person.accounts << new Account(bankName: 'BOA', balance: 450)
person.accounts << new Account(bankName: 'CitiBank', balance: 300)

If you are not dealing with collections you can simply just call the field you want to access.

assert 'Richardson Heights' == person.name
assert 'Baker Street' == person.address.street
assert 22222 == person.address.zip

If you want to access a field within a collection you have to select the element:

assert 'BOA' == person.accounts[0].bankName
assert 300 == person.accounts[1].balance​​​​​​​​​
Benjamin Muschko
  • 32,442
  • 9
  • 61
  • 82
  • 2
    This is good advice, but I'm wanting to be able to take a string. Is there some way force _person."address.street"_ into returning the value of _person.address.street_ ? – Nathan Beach Mar 30 '11 at 17:04
  • You could use [Groovy's dynamic method invocation](http://groovy.codehaus.org/Dynamic+Groovy). However, it requires you to declare the method name: person."getAddress"()​."getStreet"()​​​​​​​​​​​. – Benjamin Muschko Mar 30 '11 at 17:25
  • thanks for the info and link... i'll read up about it and see if it can help here. – Nathan Beach Mar 30 '11 at 17:37
  • 1
    Here's another way that probably suits your needs much better. You can use the class [Eval](http://groovy.codehaus.org/api/groovy/util/Eval.html): Eval.x(person, "x.address.street")​​​​​​​​​​​​​​​​​. – Benjamin Muschko Mar 30 '11 at 18:16
  • 1
    refer http://stackoverflow.com/a/15632027/2015517 you can access properties of beans dynamically using ${} syntax. e.g: person.${propertyName} – Dev Blanked Mar 31 '13 at 07:48
  • @NathanBeach My answer above (using `propertyMissing`) should work for that application. – Charles Wood Nov 13 '14 at 16:12
  • Hah, just realized this is 3-1/2 years old. Cheers. – Charles Wood Nov 13 '14 at 16:17
1

You can also use propertyMissing. This is what you might call Groovy's built-in method.

Declare this in your class:

def propertyMissing(String name) {
    if (name.contains(".")) {
        def (String propertyname, String subproperty) = name.tokenize(".")
        if (this.hasProperty(propertyname) && this."$propertyname".hasProperty(subproperty)) {
            return this."$propertyname"."$subproperty"
        }
    }
}

Then refer to your properties as desired:

def properties = "property1.property2"
assert someGroovyObject."$properties" == someValue

This is automatically recursive, and you don't have to explicitly call a method. This is only a getter, but you can define a second version with parameters to make a setter as well.

The downside is that, as far as I can tell, you can only define one version of propertyMissing, so you have to decide if dynamic path navigation is what you want to use it for.

Charles Wood
  • 864
  • 8
  • 23
0

See

https://stackoverflow.com/a/15632027/2015517

It uses ${} syntax that can be used as part of GString

Community
  • 1
  • 1
Dev Blanked
  • 8,555
  • 3
  • 26
  • 32