PROBLEM
Given two beans…
class A { String name LocalDateTime localDateTime } class B { String name LocalDateTime localDateTime }
There are several ways to copy properties from one bean to another:-
- The most rudimentary way is to “get” each property from one bean and “set” it on another bean, which is VERY verbose and stupid.
- Another way is to leverage utilities such as
BeanUtils
provided by either Apache Commons or Spring. While both libraries are calledBeanUtils
, they behave slightly different from one another. - Write home-grown reflection function… and now you have two problems: 1) it may not handle edge cases properly and 2) no one understands your implementation.
SOLUTION
Groovy provides a helper class to solve this problem called InvokerHelper
. The advantage of using this is there’s no need to import yet another dependency and it still allows us to keep our code concise.
Scenario 1: Both beans have exact properties
class MySpec extends Specification { class A { String name LocalDateTime localDateTime } class B { String name LocalDateTime localDateTime } def "given a and b with same exact properties, should copy all properties"() { given: def a = new A(name: 'name', localDateTime: LocalDateTime.now()) def b = new B() when: InvokerHelper.setProperties(b, a.properties) then: b.name == a.name b.localDateTime == a.localDateTime } }
Scenario 2: Source bean has additional properties
class MySpec extends Specification { class A { String name LocalDateTime localDateTime Integer extra1 Boolean extra2 } class B { String name LocalDateTime localDateTime } def "given a has additional properties than b, should ignore additional properties"() { given: def a = new A(name: 'name', localDateTime: LocalDateTime.now(), extra1: 1, extra2: true) def b = new B() when: InvokerHelper.setProperties(b, a.properties) then: b.name == a.name b.localDateTime == a.localDateTime } }
Scenario 3: Destination bean has additional properties
class MySpec extends Specification { class A { String name LocalDateTime localDateTime } class B { String name LocalDateTime localDateTime Integer extra1 Boolean extra2 } def "given b has additional properties than a, should set additional properties as null"() { given: def a = new A(name: 'name', localDateTime: LocalDateTime.now()) def b = new B() when: InvokerHelper.setProperties(b, a.properties) then: b.name == a.name b.localDateTime == a.localDateTime b.extra1 == null b.extra2 == null } }
Scenario 4: Same property but different data type from each bean
The short answer is don’t do it. It’s not worth the hassle and confusion.
class MySpec extends Specification { class A { String number } class B { Integer number } def "given same property name but different data type, should go bat shit crazy"() { given: def a = new A(number: '0') def b = new B() when: InvokerHelper.setProperties(b, a.properties) then: b.number == 48 // ASCII value for character '0' } def "given same property name but different data type, should go bat shit crazy again"() { given: def a = new A(number: '10') def b = new B() when: InvokerHelper.setProperties(b, a.properties) then: thrown ClassCastException // because there's no ASCII value for character '10' } }
Thank you. BeanUtils does not play nice with Groovy. InvokeHelper was the answer.