首页 > 解决方案 > 在 freemarker-template 中使用宏进行递归类型检查

问题描述

我想递归遍历 LinkedHashMap 的键和值,并使用 apache freemarker 将它们打印到模板中。LinkedHashMap 包含作为字符串的键和作为对象的值。这些值可以是 LinkedHashMap、ArrayList 或 String。

以下 java 源代码返回我想使用 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();
    }

输出是:

<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>

freemarker 模板包含以下部分。

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

  <#macro printLinkedHashMap obj>
    <#if obj??>
      <ul>
      <#if obj.key??>  
        <#if obj.key?is_string>
          <li>${obj.key} = 
        </#if>
      </#if>

      <#if obj.value??>

        <#if obj.value?is_hash_ex>
          </li>
          <#list obj.value.entrySet() as entry>
            <@printLinkedHashMap entry/>
          </#list>

        <#elseif obj.value?is_collection>
          </li>
          <ul>
            <#list obj.value as current>
              <li>${current.value}</li>
            </#list>
          </ul>

        <#elseif obj.value?is_string>
          ${obj.value}</li>
        </#if>
      </#if>
      </ul>
    </#if>  
  </#macro>

</#list>

当我使用此模板时,将显示以下错误:

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]
----

该错误表明“obj.value.entrySet()”似乎为空。但我不明白为什么会发生这种情况以及如何解决这个问题。

解决方案
我为该问题创建了一个解决方案。如果其他人需要它,我的 ftl 文件现在包含以下宏:

<#macro printLinkedHashMap entry>
  <#if entry.key??>
    <ul>
    <#if entry.key??>  
      <#if entry.key?is_string>
        <li>${entry.key} = 
      </#if>
    </#if>

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

标签: javarecursionfreemarkertypechecking

解决方案


最可能的原因是HashMap您的结构中某处有 a 而不是LinkedHashMap.

HashMapfreemarker 包装成 a SimpleHash,它没有entrySet()方法,这会导致错误obj.value.entrySet has evaluated to null or missing

请注意,您应该更喜欢使用map?keys来迭代您的地图。它适用于HashMapLinkedHashMap(并以正确的顺序返回键LinkedHashMap)。


推荐阅读