classgroovypropertiesproperty-listexpandometaclass

Copy Groovy class properties


I want to copy object properties to another object in a generic way (if a property exists on target object, I copy it from the source object).

My code works fine using ExpandoMetaClass, but I don't like the solution. Are there any other ways to do this?

class User {
    String name = 'Arturo'
    String city = 'Madrid'
    Integer age = 27
}

class AdminUser {
    String name
    String city
    Integer age
}

def copyProperties(source, target) {
    target.properties.each { key, value ->
        if (source.metaClass.hasProperty(source, key) && key != 'class' && key != 'metaClass') {
            target.setProperty(key, source.metaClass.getProperty(source, key))
        }
    }
}

def (user, adminUser) = [new User(), new AdminUser()]
assert adminUser.name == null
assert adminUser.city == null
assert adminUser.age == null

copyProperties(user, adminUser)
assert adminUser.name == 'Arturo'
assert adminUser.city == 'Madrid'
assert adminUser.age == 27

Solution

  • I think your solution is quite good and is in the right track. At least I find it quite understandable.

    A more succint version of that solution could be...

    def copyProperties(source, target) {
        source.properties.each { key, value ->
            if (target.hasProperty(key) && !(key in ['class', 'metaClass'])) 
                target[key] = value
        }
    }
    

    ... but it's not fundamentally different. I'm iterating over the source properties so I can then use the values to assign to the target :). It may be less robust than your original solution though, as I think it would break if the target object defines a getAt(String) method.

    If you want to get fancy, you might do something like this:

    def copyProperties(source, target) {
        def (sProps, tProps) = [source, target]*.properties*.keySet()
        def commonProps = sProps.intersect(tProps) - ['class', 'metaClass']
        commonProps.each { target[it] = source[it] }
    }
    

    Basically, it first computes the common properties between the two objects and then copies them. It also works, but I think the first one is more straightforward and easier to understand :)

    Sometimes less is more.