I would like to create a Java Freemarker variable that works in a template both as
${myListOfMapsVar} <#-- calls toString() on myListOfMapsVar -->
and
<#list myListOfMapsVar as item>
${item.mapKeyName}
</#list>
It seems like this should be possible using something like
List<Map> myListOfMaps = ...
var myListOfMapsVar = new MyWrappedObject();
myListOfMapsVar.wrap( myListOfMaps );
class MyWrappedObject extends DefaultObjectWrapper {
MyWrappedObject() { super(); }
public String toString() {
return "a string representation of my list";
}
}
However, when I try this, the ${myListOfMapsVar} works as expected, but the list enumeration produces an error:
The value you try to list is an extended_hash+string (.... wrapped into f.e.b.StringModel), thus you must specify two loop variables after the "as"; one for the key, and another for the value, like <#... as k, v>).
What am I doing wrong?
btw, I cannot change any application-wide Freemarker configuration.
For those that come later, here's my solution
class MyListOfMap<K,V> implements TemplateSequenceModel, TemplateScalarModel {
DefaultObjectWrapper wrapper = new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_27).build();
List<Map<K,V>> listOfMaps;
Function<Map<K,V>,String> mapper;
MyListOfMap( List list, Function<Map<K,V>,String> mapper ) {
listOfMaps = list;
this.mapper = mapper;
}
@Override
public String getAsString() throws TemplateModelException {
var strings = listOfMaps.stream().map( mapper ).collect(Collectors.toList());
return String.join( ", ", strings );
}
@Override
public TemplateModel get(int index) throws TemplateModelException {
return new MapModel( listOfMaps.get(index), wrapper );
}
@Override
public int size() throws TemplateModelException {
return listOfMaps.size();
}
}
var mylist = new ArrayList<Map<String,String>>();
mylist.add( Map.of( "first", "John", "last", "Smith", "age", "32", "city", "Chicago", "State", "IL" ) );
mylist.add( Map.of( "first", "Jane", "last", "Doe", "age", "29", "city", "Memphis", "State", "TN" ) );
mylist.add( Map.of( "first", "Mark", "last", "Roy", "age", "38", "city", "Boston", "State", "MA" ) );
var listOfPeople = new MyListOfMap<String,String>( mylist,
(person) -> person.get("first") + " " + person.get("last"));
var dataModel.put( "people", listOfPeople );
Variable 'people' can be used as a 'summary' string and as a list of hashes
myftltemplate.ftl:
${people} <#-- evaluates to: John Smith, Jane Doe, Mark Roy -->
and can also be used like
<#list people as person>
<tr>
<td>${person.first}</td>
<td>${person.last}</td>
<td>${person.age}</td>
<td>${person.city}</td>
<td>${person.state}</td>
</tr>
</#list>
Thanks to @ddekany for the pointers.