How to iterate over a map with non-string keys

3,730 views
Skip to first unread message

smallufo

unread,
Feb 21, 2014, 3:48:06 AM2/21/14
to ninja-f...@googlegroups.com
It seems FreeMarker cannot iterate over non-string keys.
A simple example is Map<MyObj , Long> myMap , representing some statistic data

<#list myMap?keys as myObj>
    ${myObj.getName()} : ${myMap[myObj]}
</#list>

This just doesn't work , the value part just evaluates to null.
I read the document here :

I read it many times but still don't know how to solve it ...
I've been stuck with this problem for the whole afternoon.

Any sample code ? Thanks a lot.

Raphael André Bauer

unread,
Feb 22, 2014, 8:51:40 AM2/22/14
to ninja-f...@googlegroups.com
hey smallufo,


rendering a map directly is problematic. Ninja uses map as type to
render all kinds of things inside the template (like i18n stuff,
sessions and so on). If you tell Ninja to render a map it will simply
add all entries of the map to the "big" internal map which is not what
you want.

Therefore you have to convert your map to a list for instance which is
then simple to render.

eg.

List<Map.Entry<SimplePojo, String>> result = new ArrayList(map.entrySet());
return Results.html().render("map", result);

and then

<#list map as entry>
${entry.getKey()} : ${entry.getValue()}<br/>
</#list>


We should add a better description to the freemarker chapter to clarify that...

Cheers,

Raphael
ps. If you want to know more about the internals check out method
"render(...)" in Result.java and TemplateEngineFreemarker.java. It's
not really complicated.
> --
> You received this message because you are subscribed to the Google Groups
> "ninja-framework" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to ninja-framewo...@googlegroups.com.
> To post to this group, send email to ninja-f...@googlegroups.com.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/ninja-framework/CAKJWkkEYzY%3DQhk5Yjjg7ZUL4%2BWyw42oemo6883QznFSf-ZPXoQ%40mail.gmail.com.
> For more options, visit https://groups.google.com/groups/opt_out.



--
inc: http://ars-machina.raphaelbauer.com
tech: http://ars-codia.raphaelbauer.com
web: http://raphaelbauer.com

Raphael André Bauer

unread,
Feb 22, 2014, 12:34:23 PM2/22/14
to ninja-f...@googlegroups.com
Hey smallufo,


after thinking more about it I think the problem is your map. Because
if you have a simple
Map<String, String> simpleMap... it works without any problems
rendering that in Ninja using something like:

return Results.html().render("mapSimple", mapSimple);

<#list mapSimple?keys as entry>
${entry} : ${mapSimple[entry]}<br/>
</#list>

Therefore I am absolutely sure that Ninja's rendering works nicely
even with maps.

If you have a map like Map<MyObj , Long> myMap

your MyObj key must correctly implement hashCode / equals, otherwise
getting a value via the key simply cannot work. And this is what
Freemarker tells you. Check out:
http://stackoverflow.com/questions/1894377/understanding-the-workings-of-equals-and-hashcode-in-a-hashmap

Once you implement hashCode, equals correctly I am sure that it will work.


Cheers,


Raphael

smallufo

unread,
Feb 22, 2014, 9:41:20 PM2/22/14
to ninja-f...@googlegroups.com
Hi , the above example , only the List<Map.Entry> works 
My pojo is named 'Category' , it implements hashCode() and equals() correctly. 

The original method (?keys) still not working.

The code :


Category category = categoryDao.get(id);
Map<Category , Long> catMap = categoryDao.getSubCategories(category).stream() .collect(Collectors.toMap( cat -> cat , cat -> categoryDao.getChildCount(cat)));

catMap.forEach( (cat , cnt) -> logger.info("subCat '{}' has {} children" , cat.getName() , cnt) ); // correctly printed.

return Results.html()
  .render("category" , category)
  .render("catMap" , catMap)
  .render("subCats" , new ArrayList(catMap.entrySet()));

And…

The catMap in the view layer still cannot print : 

<#list catMap?keys as subCat>
  ${subCat.getName()} : ${catMap[subCat]}
</#list>

The subCat.getName() is OK , but catMap[subCat] throws error :
The following has evaluated to null or missing: ==> catMap[subCat] [in template "views/App/category.ftl.html" at line 29, column 27]

Then , the subCats ( List of Map.Entry ) works well
<#list subCats as entry>
  ${entry.getKey().getName()} : ${entry.getValue()}
</#list>

I hope it can directly handle Map , no need to transform to List<Map.Entry>.






Raphael André Bauer

unread,
Feb 23, 2014, 4:21:44 AM2/23/14
to ninja-f...@googlegroups.com
Can you try if this works?

<#list map?keys as key>
${key.content} ${map?values[key_index]} <br/>
</#list>

Cheers,

Raphael
> https://groups.google.com/d/msgid/ninja-framework/CAKJWkkH-WKWWs-yEda6MMW5%3DLaX5JrYkBMrf9NUm9SoouDpfiQ%40mail.gmail.com.

smallufo

unread,
Feb 23, 2014, 4:40:51 AM2/23/14
to ninja-f...@googlegroups.com
Hi : 

in the controller :

.render("catMap" , catMap) // catMap is Map<Category,Long>

and in the view layer :
${key.content}  will surely unable to evaluate , because key is Category object (with a getName() method ) . 

<#list catMap?keys as key>
  ${key.getName()}  // it correctly calls Category.getName()
</#list>


and this surely fails :
<#list catMap?keys as key>
  ${key.content}
</#list>

because Category doesn't have 'content' field or getContent() method within.
So freemarker reports :
The following has evaluated to null or missing: ==> key.content [in template "views/App/category.ftl.html" at line 20, column 5]





Raphael André Bauer

unread,
Feb 23, 2014, 12:42:51 PM2/23/14
to ninja-f...@googlegroups.com

true true .content was just a field of my test object.

But does the following work for you?

<#list map?keys as key>
${key} ${map?values[key_index]} <br/>
</#list>

The important part is  ${map?values[key_index]} - this should allow you to access the value via the key in your map...

Cheers,


Raphael


smallufo

unread,
Feb 23, 2014, 1:29:17 PM2/23/14
to ninja-f...@googlegroups.com

2014-02-24 1:42 GMT+08:00 Raphael André Bauer <raphael.a...@gmail.com>:

<#list map?keys as key>
${key} ${map?values[key_index]} <br/>
</#list>


ya , it works !!!
Thanks a lot…
Though it is somehow not so intuitive .

Reply all
Reply to author
Forward
0 new messages