crazy4groovy crazy4groovy - 1 month ago 19
Groovy Question

Instantiate an @Immutable Groovy object with a Map?

Groovy has a neat syntax for hydrating a POGO with a Map, like:

class Person {
Address address
}
class Address {
String city
}

Person p = new Person([address: [city: 'Toronto']])
assert p.address.city == 'Toronto'


Even a deeply nested model works! I've tried doing so with an @Immutable model, to no avail:

@groovy.transform.Immutable
class Person {
Address address
}
@groovy.transform.Immutable
class Address {
String city
}

//works:
Address a = new Address('Toronto')
Person p = new Person(a)
assert p.address.city == 'Toronto'

//not works:
Person p = new Person([address: [city: 'Toronto']])
// ==> java.lang.RuntimeException: @Immutable processor doesn't know how to handle field 'address' of type 'java.util.LinkedHashMap' while constructing class Person.


Doing such is particularly awesome going from JSON -> Map -> POGO.

Any ideas how?

Answer

The @Immutable annotation alone isn't sufficient to let Groovy know that it can truly construct an immutable object, since it is possible that the declared properties might themselves be mutable. Thus, all properties must be "known" to be immutable at the time that the script is run.

Now, for reasons I don't understand, it is not sufficient to declare a property's class to be immutable in the same script. You must declare the property to be immutable within the class declaration itself, using either the knownImmutableClasses parameter:

@groovy.transform.Immutable(knownImmutableClasses = [Address])
class Person {
    Address address
}

or the knownImmutables parameter:

@groovy.transform.Immutable(knownImmutables = ['address'])
class Person {
    Address address
}

With either of these changes to the Person class declaration, your script should run exactly as expected.