在Groovy中将对象属性复制到另一个对象

4

我之前使用了一个在这个链接中提到的奇怪的方式来实现它: https://dev59.com/W2ox5IYBdhLWcg3wpFz5#9072974 所以我的代码是:

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

当我尝试调用一个将实体转换为 DTO 的方法时,出现了以下错误信息:

java.util.ArrayList.keySet() 的方法签名不适用于参数类型:(),可选解决方案包括:toSet()、toSet()、set(int, java.lang.Object)、set(int, java.lang.Object)、get(int)、get(int)

更新: 我的源代码是一个可序列化的 Bean,其字段包括:
private String passengerName;
@NotNull
@Size(min = 5, max = 40)
private String destination;
@NotNull
private String departureDate;

我的目标是一个具有相同字段的JPA实体,但具有额外的@Id字段和略微不同的日期表示:

@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
ZonedDateTime departureDate  

1
你能贴出你的实体类和数据传输对象类吗?同时展示一个将一个类复制到另一个类的示例会很有帮助。 - Szymon Stepniak
1
可能与您调用copyProperties的方式有关。我将您的代码与原始线程混合在一起,它能够正常工作。 - Michael Easter
无论如何,我会选择那篇帖子中的第一个版本。properties也会返回值(例如昂贵的getter)。 - cfrick
顺便说一下,第一个版本可行。 - yuranos
2个回答

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

3

代码是可行的,但是在某些边角情况下可能会出现问题。

为了修复这个问题,请将属性访问properties替换为方法调用getProperties(),这可能对您的情况已经足够。要覆盖所有情况,您需要编写特殊情况的代码(请参见底部)。

原始版本的工作示例

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

引起问题的示例

如果参数有一个名为properties的属性,当值为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

在两种情况下均适用的内容:

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

这里我明确调用了getProperties,而不是直接访问properties属性。

我们仍然可以打断它

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)

通过显式地使用metaClass,您可以修复最后一个示例中的e

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] }
}

然而,由于 f 的存在,那将会失败。

处理特殊情况

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] }
}

在这里,我们给需要特别处理的对象提供它们所需的内容 ;) 请注意,这仅适用于使用@CompileDynamic的Groovy,因为调用哪个getProperties实现将在运行时进行决策。另一种选择是对所有情况进行instanceof检查。


多么详细的回答!谢谢,@Mene! - yuranos
很有趣,希望能有所帮助 ;) - Mene

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接