Freemarker iterando sobre claves hashmap


87

Freemarker tiene dos tipos de datos de colección, listas y hashmaps. ¿Hay alguna forma de iterar sobre las claves de hashmap como lo hacemos con las listas?

Entonces, si tengo una var con datos, digamos:

user : {
  name : "user"
  email : "looser@everything.com"
  homepage : "http://nosuchpage.org"
}

Me gustaría imprimir todas las propiedades del usuario con su valor. Esto no es válido, pero el objetivo es claro:

<#list user.props() as prop>
  ${prop} = ${user.get(prop)}
</#list>

Respuestas:


106

Editar: No use esta solución con FreeMarker 2.3.25 y versiones posteriores, especialmente no .get(prop). Vea otras respuestas.

Utiliza la función de teclas incorporadas, por ejemplo, esto debería funcionar:

<#list user?keys as prop>
    ${prop} = ${user.get(prop)}
</#list>  

4
la sintaxis es diferente en la última versión, como se ilustra en el enlace que publiqué en mi respuesta. Me doy cuenta de que esta es una pregunta antigua, pero aparece altamente clasificada en Google.
Nick Spacek

26
Sólo una nota - se puede utilizar ${user[prop]}como una abreviatura
Bozho

Esta es una pérdida de rendimiento: para cada clave, necesita recuperar el valor. Iterar sobre el entrySet () no tiene ese problema.
Geoffrey De Smet

4
debe ser $ {user [prop]}
dns

Con los valores predeterminados de configuración, user[prop]funciona tanto como propsea String(de lo contrario, lo necesita user?api.get(prop)actualmente), pero tenga cuidado, algunos marcos (como Struts, creo) usan una configuración ahora obsoleta donde los nombres de los métodos se mezclan con las Mapclaves, y si el valor de propssucede sea ​​un nombre de método en el userobjeto Java, obtendrá el método en lugar de lo que quiso decir. Es también por eso que en esas configuraciones heredadas siempre usan user.get(prop).
ddekany

52

FYI, parece que la sintaxis para recuperar los valores ha cambiado de acuerdo con:

http://freemarker.sourceforge.net/docs/ref_builtins_hash.html

<#assign h = {"name":"mouse", "price":50}>
<#assign keys = h?keys>
<#list keys as key>${key} = ${h[key]}; </#list>

2
¿En qué se diferencia esta sintaxis?
Parker

1
buena respuesta ;-) tenga en cuenta que es posible que tenga que comprobar si hay un valor nulo al imprimir su valor, <#if h [clave] ??> $ {clave} = $ {h [clave]}; </ # if>
Brad Parks

1
La sintaxis no se modificó. Ambos [key]y .get(key)existe desde la antigüedad. .get(key)no es especial para FTL, solo está llamando a ese método público de Java. Pero solo puede usarlo si FreeMarker se configuró para exponer Mapmétodos.
ddekany

Al iterar, obtengo métodos (getClass, hashCode, equals, get, toString, class) ... sin embargo, no veo ninguna de las propiedades como 'id', que es de lo que quiero obtener una lista. ¿Alguna sugerencia de cómo obtener esa lista de propiedades que no son métodos de ese hash? Necesito saber los nombres de esas propiedades. : |
MaxRocket

47

Desde 2.3.25, hazlo así:

<#list user as propName, propValue>
  ${propName} = ${propValue}
</#list>

Tenga en cuenta que esto también funciona con claves que no son cadenas (a diferencia de map[key], que tenía que escribirse como map?api.get(key)entonces).

Antes de 2.3.25, la solución estándar era:

<#list user?keys as prop>
  ${prop} = ${user[prop]}
</#list>

Sin embargo, algunas integraciones de FreeMarker realmente antiguas usan una configuración extraña, donde los Mapmétodos públicos (como getClass) aparecen como claves. Eso sucede porque están usando un puro BeansWrapper(en lugar de DefaultObjectWrapper) cuya simpleMapWrapperpropiedad se dejó false. Debe evitar esta configuración, ya que mezcla los métodos con Mapentradas reales . Pero si llegas a tener tal configuración desafortunado, la manera de escapar de la situación está usando los métodos de Java expuestas, tales como user.entrySet(), user.get(key), etc., y no usar las construcciones del lenguaje como plantilla ?keyso user[key].


Esto funciona perfectamente. Pero veo errores en el IDE de springsource. ¿Alguna idea de cómo arreglarlo? Gracias
harshavmb

@harshavmb ¿Qué errores? ¿Utiliza quizás un complemento de FreeMarker obsoleto, que viene con una versión antigua de FreeMarker?
ddekany

No lo creo. He descargado el último de las herramientas de jboss. Probaré en otra máquina y te lo haré saber.
harshavmb

@harshavmb Si ingresa algo como ${x?nosuchthing}y pasa el cursor sobre él, el mensaje de error que se muestra le dirá qué versión de FreeMarker usa. Debería serlo 2.3.25-incubating.
ddekany

extraño, lo intenté en Mac y no pude replicar el problema. El problema parece ser solo con mi vm. Echaré un vistazo a la versión jar. Sin embargo, es solo un error en el editor, pero el código se ejecutó correctamente.
harshavmb

12

Si usa un BeansWrapper con un nivel de exposición de Expose.SAFE o Expose.ALL, entonces se puede emplear el enfoque estándar de Java para iterar el conjunto de entradas:

Por ejemplo, lo siguiente funcionará en Freemarker (desde al menos la versión 2.3.19):

<#list map.entrySet() as entry>  
  <input type="hidden" name="${entry.key}" value="${entry.value}" />
</#list>

En Struts2, por ejemplo, se usa una extensión de BeanWrapper con el nivel de exposición predeterminado para permitir esta forma de iteración.


3
¿De verdad has probado esto? Porque tengo unInvalidReferenceException cuando lo probé, mientras map?keystrabajaba.
kdgregory

4
Esto solo funciona cuando se usa freemarker.ext.beans.BeansWrappercomo envoltorio de objetos. De lo contrario, Maps se ajustará automáticamente a un SimpleHashobjeto que no admite #entrySet(). (ver freemarker.sourceforge.net/docs/api/freemarker/template/… )
Koraktor

Tiene razón y he actualizado mi respuesta para reflejar su comentario. ¡Bien cuidado!
rees

1
Lo anterior no funcionará tan bien para el hash creado dentro de FTL, especialmente si está utilizando el resolutor Spring Freemarker con BeanWrapper. El hash declarado dentro del archivo Ftl no está empaquetado y seguirá siendo solo un hash iterable usando las claves?
skipy

1
No utilice pure BeansWrapper, al menos no con sus valores predeterminados, donde simpleMapWrapperestá false. Se vuelve muy confuso, ya que mezcla claves con nombres de métodos. Si necesita llamar entrySet(), siga usando un contenedor de objetos "limpio", como el predeterminado, y escriba map?api.entrySet()si necesita acceder a la API de Java en lugar de las claves.
ddekany

2

Objetos iterativos

Si sus claves de mapa son un objeto y no una cadena, puede iterarlas usando Freemarker.

1) Convierta el mapa en una lista en el controlador:

List<Map.Entry<myObjectKey, myObjectValue>> convertedMap  = new ArrayList(originalMap.entrySet());

2) Itere el mapa en la plantilla Freemarker, accediendo al objeto en la Clave y al Objeto en el Valor:

<#list convertedMap as item>
    <#assign myObjectKey = item.getKey()/>
    <#assign myObjectValue = item.getValue()/>
    [...]
</#list>

1

Para completar, vale la pena mencionar que hay un manejo decente de colecciones vacías en Freemarker desde hace poco.

Entonces, la forma más conveniente de iterar un mapa es:

<#list tags>
<ul class="posts">
    <#items as tagName, tagCount>
        <li>{$tagName} (${tagCount})</li>
    </#items>
</ul>
<#else>
    <p>No tags found.</p>
</#list>

No más <#if ...>envoltorios.


La mejor respuesta. Gracias.
egemen

0

Puede utilizar una comilla simple para acceder a la clave que estableció en su programa Java.

Si configura un mapa en Java como este

Map<String,Object> hash = new HashMap<String,Object>();
hash.put("firstname", "a");
hash.put("lastname", "b");

Map<String,Object> map = new HashMap<String,Object>();
map.put("hash", hash);

Luego puede acceder a los miembros de 'hash' en Freemarker de esta manera:

${hash['firstname']}
${hash['lastname']}

Salida:

a
b

que muestra cómo abordar claves individuales, pero la pregunta pregunta cómo iterar
Lambart
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.