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 : "[email protected]"
  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>
tzador
fuente

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>  
skaffman
fuente
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>
Nick Spacek
fuente
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].

ddekany
fuente
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.

rees
fuente
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>
Tk421
fuente
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.

Ondra Žižka
fuente
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
Ashish Chhabria
fuente
que muestra cómo abordar claves individuales, pero la pregunta pregunta cómo iterar
Lambart