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 
4
  • 1
    Could you paste your Entity and Dto classes? Also showing a sample of copying one to another would be helpful. Commented Oct 26, 2017 at 12:25
  • 1
    It might be something to do with the way you are calling copyProperties. I mashed your version with the original thread and it worked fine. Commented Oct 26, 2017 at 18:29
  • I'd go with the first version on that post anyway. properties also returns the values (e.g. expensive getters). Commented Oct 26, 2017 at 21:16
  • The first version works, by the way. Commented Oct 27, 2017 at 5:10

2 Answers 2

4
User user = User.findById('1') User copyUser = new User() InvokerHelper.setProperties(copyUser, user.properties) 
Sign up to request clarification or add additional context in comments.

Comments

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.

2 Comments

What a detailed answer! Thanks, @Mene!
Was fun, hope it helps ;)

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.