class - Copie las propiedades de la clase Groovy
properties property-list (4)
Quiero copiar propiedades de objetos a otro objeto de forma genérica (si existe una propiedad en el objeto de destino, la copio del objeto de origen).
Mi código funciona bien usando ExpandoMetaClass , pero no me gusta la solución. ¿Hay alguna otra forma de hacer esto?
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
Creo que tu solución es bastante buena y está en el camino correcto. Al menos, me resulta bastante comprensible.
Una versión más sucinta de esa solución podría ser ...
def copyProperties(source, target) {
source.properties.each { key, value ->
if (target.hasProperty(key) && !(key in [''class'', ''metaClass'']))
target[key] = value
}
}
... pero no es fundamentalmente diferente. Estoy iterando sobre las propiedades de origen para poder usar los valores para asignar al objetivo :). Sin embargo, puede ser menos robusto que tu solución original, ya que creo que se rompería si el objeto objetivo define un getAt(String)
.
Si quieres ser elegante, puedes hacer algo como esto:
def copyProperties(source, target) {
def (sProps, tProps) = [source, target]*.properties*.keySet()
def commonProps = sProps.intersect(tProps) - [''class'', ''metaClass'']
commonProps.each { target[it] = source[it] }
}
Básicamente, primero calcula las propiedades comunes entre los dos objetos y luego los copia. También funciona, pero creo que el primero es más sencillo y más fácil de entender :)
A veces menos es más.
Otra forma es hacer:
def copyProperties( source, target ) {
[source,target]*.getClass().declaredFields*.grep { !it.synthetic }.name.with { a, b ->
a.intersect( b ).each {
target."$it" = source."$it"
}
}
}
Que obtiene las propiedades comunes (que no son campos sintéticos) y luego las asigna al objetivo
También podría (utilizando este método) hacer algo como:
def user = new User()
def propCopy( src, clazz ) {
[src.getClass(), clazz].declaredFields*.grep { !it.synthetic }.name.with { a, b ->
clazz.newInstance().with { tgt ->
a.intersect( b ).each {
tgt[ it ] = src[ it ]
}
tgt
}
}
}
def admin = propCopy( user, AdminUser )
assert admin.name == ''Arturo''
assert admin.city == ''Madrid''
assert admin.age == 27
Así que le pasas al método un objeto para copiar las propiedades y la clase del objeto devuelto. El método luego crea una nueva instancia de esta clase (suponiendo que es un constructor sin args), establece las propiedades y las devuelve.
Editar 2
Suponiendo que se trata de clases Groovy, puede invocar el constructor del Map
y establecer todas las propiedades comunes de esta manera:
def propCopy( src, clazz ) {
[src.getClass(), clazz].declaredFields*.grep { !it.synthetic }.name.with { a, b ->
clazz.metaClass.invokeConstructor( a.intersect( b ).collectEntries { [ (it):src[ it ] ] } )
}
}
Creo que la mejor y más clara manera es usar el método InvokerHelper.setProperties
Ejemplo:
import groovy.transform.ToString
import org.codehaus.groovy.runtime.InvokerHelper
@ToString
class User {
String name = ''Arturo''
String city = ''Madrid''
Integer age = 27
}
@ToString
class AdminUser {
String name
String city
Integer age
}
def user = new User()
def adminUser = new AdminUser()
println "before: $user $adminUser"
InvokerHelper.setProperties(adminUser, user.properties)
println "after : $user $adminUser"
Salida:
before: User(Arturo, Madrid, 27) AdminUser(null, null, null)
after : User(Arturo, Madrid, 27) AdminUser(Arturo, Madrid, 27)
Nota : Si quieres más legibilidad, puedes usar la categoría
use(InvokerHelper) {
adminUser.setProperties(user.properties)
}
Spring BeanUtils.copyProperties funcionará incluso si las clases de origen / destino son de tipos diferentes. http://docs.spring.io/autorepo/docs/spring/3.2.3.RELEASE/javadoc-api/org/springframework/beans/BeanUtils.html