Freemarker iterando sobre chaves de hashmap

87

O Freemarker tem dois tipos de dados de coleção, listas e hashmaps. Existe uma maneira de iterar as chaves de hashmap da mesma forma que fazemos com as listas?

Então, se eu tiver uma var com dados, digamos:

user : {
  name : "user"
  email : "[email protected]"
  homepage : "http://nosuchpage.org"
}

Gostaria de imprimir todas as propriedades do usuário com seus valores. Isso é inválido, mas o objetivo é claro:

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

Respostas:

106

Edit: Não use esta solução com FreeMarker 2.3.25 e superior, especialmente não .get(prop). Veja outras respostas.

Você usa a função de teclas integradas, por exemplo, isso deve funcionar:

<#list user?keys as prop>
    ${prop} = ${user.get(prop)}
</#list>  
skaffman
fonte
4
a sintaxe é diferente na versão mais recente, conforme ilustrado no link que postei em minha resposta. Sei que essa é uma pergunta antiga, mas aparece bem classificada no Google.
Nick Spacek
26
apenas uma nota - você pode usar ${user[prop]}como uma abreviação
Bozho
Este é um vazamento de desempenho: para cada chave, ela precisa recuperar o valor. Iterar sobre o entrySet () não tem esse problema.
Geoffrey De Smet,
4
deve ser $ {user [prop]}
dns
Com os padrões de configuração user[prop]funciona tanto quanto propa String(caso contrário, você precisa user?api.get(prop)atualmente), mas cuidado, alguns frameworks (como Struts, eu acredito) usam uma configuração obsoleta onde nomes de métodos são misturados com Mapchaves, e se o valor de propsacontecer a ser um nome de método no userobjeto Java, você obtém o método em vez do que você quis dizer. É também por isso que nessas configurações legadas eles sempre usam user.get(prop).
ddekany
52

Para sua informação, parece que a sintaxe para recuperar os valores mudou de acordo com:

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
fonte
2
Como essa sintaxe é diferente?
Parker de
1
boa resposta ;-) note que você pode ter que fazer uma verificação para um nulo ao imprimir seu valor, <#if h [key] ??> $ {key} = $ {h [key]}; </ # if>
Brad Parks
1
A sintaxe não foi alterada. Ambos [key]e .get(key)existe desde os tempos antigos. .get(key)não é especial para FTL, é apenas chamar aquele método Java público. Mas você só pode usá-lo se o FreeMarker foi configurado para expor Mapmétodos.
ddekany
Ao iterar, eu obtenho métodos (getClass, hashCode, equals, get, toString, class) ... entretanto, não estou vendo nenhuma das propriedades como 'id', que é o que desejo obter uma lista. Alguma sugestão de como obter essa lista de propriedades não-método desse hash? Preciso saber esses nomes de propriedade. : |
MaxRocket 01 de
47

Desde 2.3.25, faça assim:

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

Observe que isso também funciona com chaves que não sejam strings (ao contrário map[key], que precisavam ser escritas na map?api.get(key)época).

Antes de 2.3.25, a solução padrão era:

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

No entanto, algumas integrações FreeMarker realmente antigas usam uma configuração estranha, onde os Mapmétodos públicos (como getClass) aparecem como chaves. Isso acontece porque eles estão usando um puro BeansWrapper(em vez de DefaultObjectWrapper) cuja simpleMapWrapperpropriedade foi deixada ativada false. Você deve evitar tal configuração, pois mistura os métodos com Mapentradas reais . Mas, se você tiver tal configuração infeliz, a maneira de escapar a situação está usando os métodos Java expostas, tais como user.entrySet(), user.get(key), etc., e não usando as construções de linguagem modelo de como ?keysou user[key].

ddekany
fonte
Isso funciona perfeitamente. Mas, estou vendo erros no IDE do springsource. Alguma ideia de como consertá-lo? Obrigado
harshavmb de
@harshavmb Quais erros? Ele usa um plugin do FreeMarker desatualizado, que vem com uma versão antiga do FreeMarker?
ddekany de
Acho que não. Baixei o último do jboss tools. Vou tentar em outra máquina e informá-lo.
harshavmb de
@harshavmb Se você inserir algo como ${x?nosuchthing}e passar o mouse sobre ele, a mensagem de erro mostrada dirá qual versão do FreeMarker ele usa. Deve ser 2.3.25-incubating.
ddekany
estranho, acabei de experimentar no Mac e não consegui reproduzir o problema. O problema parece ser apenas com a minha vm. Vou dar uma olhada na versão jar. No entanto, é apenas um erro no editor, mas o código foi executado corretamente.
harshavmb de
12

Se estiver usando um BeansWrapper com um nível de exposição Expose.SAFE ou Expose.ALL, a abordagem Java padrão de iterar o conjunto de entrada pode ser empregada:

Por exemplo, o seguinte funcionará no Freemarker (desde pelo menos a versão 2.3.19):

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

No Struts2, por exemplo, uma extensão do BeanWrapper é usada com o nível de exposição padronizado para permitir essa forma de iteração.

rees
fonte
3
Você realmente tentou isso? Porque ganhei um InvalidReferenceExceptionquando tentei, enquanto map?keystrabalhava.
kdgregory
4
Isso só funciona quando usado freemarker.ext.beans.BeansWrappercomo wrapper de objeto. Caso contrário, Maps será automaticamente envolvido em um SimpleHashobjeto que não suporta #entrySet(). (consulte freemarker.sourceforge.net/docs/api/freemarker/template/… )
Koraktor
Você está correto e eu atualizei minha resposta para refletir seu comentário. Bem olhando!
rees
1
O procedimento acima não funcionará bem para o hash criado dentro da FTL, especialmente se você estiver usando o resolvedor Spring Freemarker com BeanWrapper. O hash declarado dentro do arquivo Ftl não é empacotado e ainda será apenas um hash iterável usando as chaves?.
skipy
1
Não use puro BeansWrapper, pelo menos não com seus padrões, onde simpleMapWrapperestá false. Isso fica muito confuso, pois mistura chaves com nomes de métodos. Se você precisar chamar entrySet(), apenas continue usando um wrapper de objeto "limpo", como o padrão, e escreva map?api.entrySet()se você precisa acessar a API Java em vez das chaves.
ddekany
2

Iterando objetos

Se as chaves do seu mapa são um objeto e não uma string, você pode iterar usando o Freemarker.

1) Converta o mapa em uma lista no controlador:

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

2) Iterar o mapa no modelo Freemarker, acessando o objeto na chave e o objeto no valor:

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

Para completar, vale a pena mencionar que há um tratamento decente de coleções vazias no Freemarker desde recentemente.

Portanto, a maneira mais conveniente de iterar um mapa é:

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

Sem mais <#if ...>invólucros.

Ondra Žižka
fonte
Melhor resposta. Obrigado.
egemen
0

Você pode usar uma aspa simples para acessar a chave que definiu em seu programa Java.

Se você definir um mapa em 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);

Então você pode acessar os membros do 'hash' no Freemarker assim -

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

Resultado :

a
b
Ashish Chhabria
fonte
que mostra como lidar com chaves individuais, mas a pergunta pergunta como iterar
Lambart