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 called BeanUtils, 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'
}
}
Leave a Reply