You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Apache FreeMarker递归遍历LinkedHashMap的模板问题及解决

Recursively Traverse Nested LinkedHashMap with Apache FreeMarker (Fix for entrySet() Null Error)

I recently ran into an issue while trying to recursively traverse a nested LinkedHashMap with Apache FreeMarker—where values could be sub-LinkedHashMaps, ArrayLists, or plain Strings. I had a working Java implementation to generate the nested HTML list output, but my initial FreeMarker template kept throwing a null error on obj.value.entrySet(). After some debugging, I fixed the macro and wanted to share the solution.

My Original Problem

I wanted to replicate the output of this Java recursive method in FreeMarker:

private String printLinkedHashMap(LinkedHashMap<?, ?> map, int counter) {
    if(map == null) { return ""; }
    StringBuilder sb = new StringBuilder();
    Set<?> set = map.entrySet();
    sb.append(getSpaces(counter) + "<ul>\n");
    for (Object object : set) {
        if(object instanceof Entry<?, ?>) {
            Entry<?, ?> entry = (Entry<?, ?>) object;
            sb.append(getSpaces(counter) + "<li>" + entry.getKey() + " = ");
            if(entry.getValue() instanceof LinkedHashMap<?, ?>) {
                sb.append("</li>\n" + printLinkedHashMap((LinkedHashMap<?, ?>) entry.getValue(), counter+1));
            } else if(entry.getValue() instanceof ArrayList<?>){
                ArrayList<?> listOfValues = (ArrayList<?>) entry.getValue();
                sb.append("</li>" + printArrayList(listOfValues, counter+1));
            } else {
                sb.append(entry.getValue().toString() + "</li>\n");
            }
        }
    }
    sb.append(getSpaces(counter) + "</ul>\n");
    return sb.toString();
}

private String printArrayList(ArrayList<?> listOfValues, int counter) {
    StringBuilder sb = new StringBuilder();
    if(listOfValues.size() > 1) {
        for (int i = 0; i < listOfValues.size(); i++) {
            sb.append('\n' + getSpaces(counter));
            sb.append("<li>" + listOfValues.get(i).toString() + "</li>");
        }
    } else {
        sb.append(listOfValues.get(0).toString());
    }
    return sb.toString();
}

Expected output (nested HTML list):

<ul>
  <li>defaultVar = </li>
  <ul>
    <li>subdefaultVar = </li>
    <ul>
      <li>subsubdefaultVar = defaultValue</li>
      <li>subsubdefaultVariable = defaultValue2</li>
    </ul>
    <li>anothersubdefaultVar = </li>
    <ul>
      <li>anothersubsubdefaultVar = anotherdefaultValue</li>
      <li>anothersubsubdefaultVariable = anotherdefaultValue2</li>
    </ul>
  </ul>
  <li>defaultVariable = Defaultshort</li>
</ul>

But my initial template threw this error:

The following has evaluated to null or missing:
==> obj.value.entrySet [in template "template.ftl" at line 55, column 26]

Tip: It's the step after the last dot that caused this error, not those before it.

Tip: If the failing expression is known to legally refer to something that's sometimes null or missing, either specify a default value like myOptionalVar!myDefault, or use <#if myOptionalVar??>when-present<#else>when-missing</#if>. (These only cover the last step of the expression; to cover the whole expression, use parenthesis: (myOptionalVar.foo)!myDefault, (myOptionalVar.foo)??

FTL stack trace ("~" means nesting-related):

  • Failed at: #list obj.value.entrySet() as entry [in template "template.ftl" in macro "printLinkedHashMap" at line 55, column 19]
  • Reached through: @printLinkedHashMap entry [in template "template.ftl" in macro "printLinkedHashMap" at line 56, column 21]
  • Reached through: @printLinkedHashMap entry [in template "template.ftl" in macro "printLinkedHashMap" at line 56, column 21]
  • Reached through: @printLinkedHashMap entry [in template "template.ftl" at line 41, column 11]

Root Causes of the Error

  1. Incorrect Macro Parameter Handling: My initial macro accepted an obj parameter, but didn't properly validate if it was a valid entry before accessing obj.value.entrySet().
  2. Unchecked entrySet() Access: Not all hash-like objects in FreeMarker support the entrySet() method, and I didn't add a null check before calling it.
  3. Broken HTML Tag Nesting: The <li> and <ul> tags were being closed at the wrong times, leading to invalid structure that compounded the error.

Fixed FreeMarker Macro

Here's the corrected macro that works as expected:

<#macro printLinkedHashMap entry>
    <#if entry.key??>
        <ul>
            <#if entry.key?is_string>
                <li>${entry.key} = </li>
            </#if>
            <#if entry.value??>
                <#if entry.value?is_hash_ex>
                    <#if (entry.value.entrySet())??>
                        <#list entry.value.entrySet() as newEntry>
                            <#if newEntry.key?? && newEntry.value??>
                                <@printLinkedHashMap newEntry/>
                            </#if>
                        </#list>
                    </#if>
                <#elseif entry.value?is_collection>
                    <ul>
                        <#list entry.value as currentValue>
                            <li>${currentValue}</li>
                        </#list>
                    </ul>
                <#else>
                    </li>${entry.value}
                </#if>
            </#if>
        </ul>
    </#if>
</#macro>

Key Fixes Explained

  • Explicit Entry Parameter: The macro now takes an entry parameter, ensuring we always work with a key-value pair from the map.
  • Defensive Null Checks: Added checks for entry.key??, entry.value??, and (entry.value.entrySet())?? to avoid null/missing value errors.
  • Proper HTML Tag Closure: Fixed the timing of closing <li> tags to match the Java implementation's structure.
  • Collection Handling: Correctly iterates over collections (like ArrayList) and outputs each element in a <li> tag.
  • Type Validation: Uses is_hash_ex to check for hash structures and is_collection for list-like values, ensuring we handle each type correctly.

How to Use It

Call the macro by iterating over your map's entry set:

<#list yourLinkedHashMap.entrySet() as entry>
    <@printLinkedHashMap entry/>
</#list>

This will generate the nested HTML list exactly as the Java method does.

内容的提问来源于stack exchange,提问作者Crazy-Tony

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.09 20:07:57