Search code examples
javatemplate-enginepebble

Cannot use for tag to iterate through Array List in Pebble Template Engine


I have been trying to iterate through an Array List of items with {% for item in items %} syntax to no avail. Keeps throwing

java.lang.UnsupportedOperationException
    at java.util.AbstractMap.put(AbstractMap.java:209)
    at com.mitchellbosecke.pebble.template.Scope.put(Scope.java:53)
    at   com.mitchellbosecke.pebble.template.ScopeChain.put(ScopeChain.java:61)
at com.mitchellbosecke.pebble.template.EvaluationContext.put(EvaluationContext.java:162) exception.

Tried with primitive arrays, maps, many types of List implementations, always this is the result. When doing an iterable test, the array list returns true, so I think it should be iterable using the for tag. Am I doing something wrong? Please find the code below.

PebbleTemplate template = pebbleEngine.getTemplate(
  "{% if menuItems is iterable %}{% for menuItem in menuItems %}" +
  " \"{{ menuItem }}\" this" +
  "{% endfor %}{% else %}nope{% endif %}");
StringWriter writer = new StringWriter();

List<String> menuItems = new ArrayList<>();
menuItems.add("menu item1");
menuItems.add("menu item2");
menuItems.add("menu item 3");

template.evaluate(writer, Collections.<String,Object>singletonMap("menuItems", menuItems));
System.out.println(writer);

Solution

  • It depends on the use of the singleton map. Use an HashMap instead and should work.

    Explanation. The Scope is initialised as follows:

    public Scope(Map<String, Object> backingMap, boolean isLocal) {
        this.backingMap = (Map)(backingMap == null?new HashMap():backingMap);
        this.isLocal = isLocal;
    }
    

    so it actually reuses the map you are providing. When it calls put over the Map, it throws an exception because it is a singleton.