Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Updating groovy object fields from a map

Tags:

groovy

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"])
like image 660
Cléssio Mendes Avatar asked Apr 14 '15 03:04

Cléssio Mendes


People also ask

How do I create a map object in Groovy?

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.

How to remove element from map in Groovy?

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.

Are Groovy maps ordered?

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.


3 Answers

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
    }
}
like image 98
Raniz Avatar answered Nov 13 '22 22:11

Raniz


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.

like image 29
Szymon Stepniak Avatar answered Nov 13 '22 22:11

Szymon Stepniak


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.

like image 5
Bill K Avatar answered Nov 13 '22 21:11

Bill K