We are trying to iterate over a Map
, but without any success. We reduced our issue to this minimal example:
def map = [ 'monday': 'mon', 'tuesday': 'tue', ]
If we try to iterate with:
map.each{ k, v -> println "${k}:${v}" }
Only the first entry is output: monday:mon
The alternatives we know of are not even able to enter the loop:
for (e in map) { println "key = ${e.key}, value = ${e.value}" }
or
for (Map.Entry<String, String> e: map.entrySet()) { println "key = ${e.key}, value = ${e.value}" }
Are failing, both only showing the exception java.io.NotSerializableException: java.util.LinkedHashMap$Entry
. (which could be related to an exception occurring while raising the 'real' exception, preventing us from knowing what happened).
We are using latest stable jenkins (2.19.1) with all plugins up-to-date as of today (2016/10/20).
Is there a solution to iterate over elements in a Map
within a Jenkins pipeline Groovy script ?
We can iterate through entries using the each() and eachWithIndex() methods. The each() method provides implicit parameters, like entry, key, and value, which correspond to the current Entry. The eachWithIndex() method also provides an index in addition to Entry. Both the methods accept a Closure as an argument.
Look up the key in this Map and return the corresponding value. If there is no entry in this Map for the key, then return null. Obtain a Set of the keys in this Map. Associates the specified value with the specified key in this Map.
Maps don't have an order for the elements, but we may want to sort the entries in the map. Since Groovy 1.7. 2 we can use the sort() method which uses the natural ordering of the keys to sort the entries. Or we can pass a Comparator to the sort() method to define our own sorting algorithm for the keys.
Its been some time since I played with this, but the best way to iterate through maps (and other containers) was with "classical" for loops, or the "for in". See Bug: Mishandling of binary methods accepting Closure
To your specific problem, most (all?) pipeline DSL commands will add a sequence point, with that I mean its possible to save the state of the pipeline and resume it at a later time. Think of waiting for user input for example, you want to keep this state even through a restart. The result is that every live instance has to be serialized - but the standard Map iterator is unfortunately not serializable. Original Thread
The best solution I can come up with is defining a Function to convert a Map into a list of serializable MapEntries. The function is not using any pipeline steps, so nothing has to be serializable within it.
@NonCPS def mapToList(depmap) { def dlist = [] for (def entry2 in depmap) { dlist.add(new java.util.AbstractMap.SimpleImmutableEntry(entry2.key, entry2.value)) } dlist }
This has to be obviously called for each map you want to iterate, but the upside it, that the body of the loop stays the same.
for (def e in mapToList(map)) { println "key = ${e.key}, value = ${e.value}" }
You will have to approve the SimpleImmutableEntry
constructor the first time, or quite possibly you could work around that by placing the mapToList function in the workflow library.
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