In groovy we can easily create objects from maps and fill the corresponding fields automatically:
def myAddress = new Address([street:"King's street", number:"1200"])
Is it possible to also update an existing object from a map without recreating it? Something like...
myAddress.update([zip: "30555050", city: "London"])
We can use the map literal syntax [k:v] for creating maps. Basically, it allows us to instantiate a map and define entries in one line. Notice that the keys aren't surrounded by quotes, and by default, Groovy creates an instance of java. util.
To remove a single item, you can use "remove" method. Now the map will be like this. To remove multiple map entries or key-value, you can use "minus" method.
In Groovy, maps created with the literal notation are ordered. We can expect our output to be in the same order as we defined in our original map.
You can use object."${variable}"
accessors to do this:
map.each { key, value ->
object."${key}" = value
}
You can then create a method that does this and install that on Object.metaClass and it will be available everywhere:
@Canonical
class MapSet {
String name
int count
static def setAttributesFromMap(Object o, Map<String, Object> map) {
map.each { key, value ->
o."${key}" = value
}
}
static void main(String[] args) {
Object.metaClass.update = {
setAttributesFromMap delegate, it
}
def o = new MapSet([
name: "foo",
count: 5
])
assert o.name == "foo"
assert o.count == 5
o.update([
name: "bar",
count: 6
])
assert o.name == "bar"
assert o.count == 6
}
}
You can use InvokeHelper category and setProperties method, here is a short example:
import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString
import org.codehaus.groovy.runtime.InvokerHelper
@EqualsAndHashCode
@ToString
class Address {
String street
String number
String city
}
Address mainAddress = new Address(street: 'Test', number: '2B', city: 'London')
use InvokerHelper, {
mainAddress.setProperties([street: 'Lorem', number: 'Ipsum'])
}
assert mainAddress.street == 'Lorem'
assert mainAddress.number == 'Ipsum'
assert mainAddress.city == 'London'
Although if you can avoid mutable objects, it's better for you. Otherwise you have to think about thread-safety to do not run into concurrency problems. You can use previous example to create a static method that expects 2 arguments: the existing object and a map of properties to update. In result you get a new instance that contains updated fields. Also you can make your class an immutable one.
After looking up/learning from Szymon's Excellent answer and finding a different way to invoke the helper, it seems like the answer can be simplified to:
InvokerHelper.setProperties(myAddress, [zip: "30555050", city: "London"])"
which is amazingly close to your requested
myAddress.update([zip: "30555050", city: "London"])
I added this as a comment to his question but it's so easy I thought it deserved a terse top-level answer of it's own.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With