-
Notifications
You must be signed in to change notification settings - Fork 19
Working with arrays, collection and maps
Mapping arrays, collections and maps is either easy as mapping ordinal properties. Elements of the collections will be converted according to the type of the target collection. Collections should be the same arity.
a.children = b.details.kids // List<Child> <-> Set<Kid>
a.options = b.details.properties // Map<String, Object> <-> Map<Integer, Object>
Sometimes you need to merge several collections into one collection/array as follows
// oneway as it is an expression
a.persons = { b.parents + b.children } // List<Person> -> List<Parent> + List<Child>
// reverse expressions if required
def isChild = { it -> it.children.isEmpty()} // a function to determine what the person is
b.children = { a.persons.filter {isChild(it)}} // List<Child> <- List<Person> filtered by the function isChild
b.parents = { a.persons.filter {!isChild(it)}} // List<Parent> <- List<Person> filtered by the function !isChild
We can access elements of a array/collection/map defining an index/key in square braces. When a Collection/Set is accessed by index the required element is retrieve using collection's iterator.
a.children[0] = b.details.favoriteKid
a.educationName = b.details.educations[0].name
a.educationDescription = b.details.educations[0].description
// the same approach is applicable to Maps
a.options["birthdate"] = b.details.birth
a.options["name"] = b.name
a.options["lastName"] = b.lastName
A negative integer (I prefer -1) can be specified as an index of an array or collection. In this case Nomin adds an element to the tail of an array/collection. The next snippet shows a possible usage.
a.options[-1] = { if (condition1) new Option1(...) }
a.options[-1] = { if (condition2) new Option2(...) }
If condition1 is false and condition2 is true we get the result as an array/collection with the only Option2 instance. This feature can be applied for writing values because there is no meaning to read elements with a negative index, so an exception will be thrown at runtime.
You can use hints or dynamic hints to specify needed types.
a.children = b.details.kids // List<Child> <-> Set<Kid>
hint a: List[ExtendedChildren], b: Set[ExtendedKid]
a.options = b.details.properties // Map<Object, Object> <-> Map<Object, Object>
hint a: Map[String, Person], b: Map[Integer, Employee] // use hint to specify types
// or
hint a: Map[String, {
it instanceof LinearManager ? DetailedPerson : Person // Person is returned if it is an Employee
}]
Here is the special case when a custom conversion is used with Map properties. A closure gets a Map.Entry value as the only parameter and should result exactly with a tuple [key, value]. The next sample shows the idea.
a.options = b.properties // options is Map<String, String> <-> Map<Integer, Object>
// just convert keys and values to strings or integers
convert to_a: { e -> [ String.valueOf(e.key), String.valueOf(e.value) ] },
to_b: { e -> [ Integer.valueOf(e.key), Integer.valueOf(e.value) ] }
Absolutely identical the above you can define conversions between a Map and a Collection/array property.
a.options = b.props // Map <=> List<String>. Strings in the list are formatted as 'propertyName=propertyValue'
convert to_a: { String s -> [ s.split("=")[0], s.split("=")[1] ] },
to_b: { e -> e.key + "=" + e.value }