Groovy: Copying Properties Between Two Beans

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'
    }
}
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s