4

I was using a funky way to do it suggested in: https://stackoverflow.com/a/9072974/4470135 So my code is:

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

What I get when I try to call a method that should convert an Entity into a Dto is:

No signature of method: java.util.ArrayList.keySet() is applicable for argument types: () values: []\nPossible solutions: toSet(), toSet(), set(int, java.lang.Object), set(int, java.lang.Object), get(int), get(int)

UPDATE:

My source is a Serializable bean with fields:

private String passengerName;
@NotNull
@Size(min = 5, max = 40)
private String destination;
@NotNull
private String departureDate;

My target is a JPA Entity with the same fields, but with an additional @Id field and a slightly different date representation:

@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
ZonedDateTime departureDate  
yuranos
  • 8,799
  • 9
  • 56
  • 65

2 Answers2

4
User user = User.findById('1')
User copyUser = new User()
InvokerHelper.setProperties(copyUser, user.properties)
3

The code is working, however, there are corner cases where it may break.

To fix this replace the property access properties with the method call getProperties(), which might be enough for your case. To cover all cases, you will need to write code for special cases (see bottom)

Working example for the original version

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

def a = new Expando()
a.foo = "foo"
a.bar = "bar"

def b = new Expando()
b.baz = "baz"
b.bar = "old"

copyProperties(a, b)

println b

Example causing problems

If the parameters have a property called properties I get the same exception you got (if the value is a List):

def c = new Expando()
c.properties = []
c.bar = "bar"

def d = new Expando()
d.baz = "baz"
d.bar = "old"

copyProperties(c, d)

println d

What works in both cases:

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

Not that here I used an explicit call to getProperties rather than just accessing the properties property.

We can still break this

def e = new Object() {
    // causes same Exception again
    def getProperties() {
        return []
    }

    def bar = "bar"
}

def f = new Expando()
f.baz = "baz"
f.bar = "old"

copyProperties(e, f)

You can fix the last example for e by using the metaClass explicitly

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

However, that will fail due to f.

Handle special cases

def getProperties(Expando obj) {
    return obj.getProperties().keySet()
}

def getProperties(Object obj) {
    return obj.getMetaClass()*.properties*.name
}

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

Here we give objects that need a special treatment what they need ;) Note that this only works like this for groovy with @CompileDynamic as the decision which getProperties implementation is called will be made at runtime. The alternative is a check with instanceof for all the cases.

Mene
  • 3,739
  • 21
  • 40