Freemarker перебирает ключи хэш-карты

87

Freemarker имеет два типа данных коллекций: списки и хэш-карты. Есть ли способ перебирать ключи хэш-карты, как мы это делаем со списками?

Итак, если у меня есть переменная с данными, скажем:

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

Я хочу напечатать все свойства пользователя с их значением. Это неверно, но цель ясна:

<#list user.props() as prop>
  ${prop} = ${user.get(prop)}
</#list>
цадор
источник

Ответы:

106

Изменить: не используйте это решение с FreeMarker 2.3.25 и выше, особенно .get(prop). Смотрите другие ответы.

Вы используете функцию встроенных клавиш , например, это должно работать:

<#list user?keys as prop>
    ${prop} = ${user.get(prop)}
</#list>  
Скаффман
источник
4
синтаксис в последней версии отличается, как показано в ссылке, которую я разместил в своем ответе. Я понимаю, что это старый вопрос, но он высоко ценится в Google.
Ник Спейсек,
26
просто примечание - вы можете использовать ${user[prop]}как стенографию
Bozho
Это утечка производительности: для каждого ключа необходимо получить значение. Итерация по entrySet () не имеет этой проблемы.
Джеффри Де Смет
4
должно быть $ {user [prop]}
dns
Со значениями конфигурации по умолчанию user[prop]работают, насколько propэто возможно String(в противном случае вам нужно user?api.get(prop)сейчас), но будьте осторожны, некоторые фреймворки (например, Struts, я считаю) используют теперь устаревшую настройку, где имена методов смешаны с Mapключами, и поэтому, если значение propsпроисходит с быть именем метода в userобъекте Java, вы получите метод вместо того, что имели в виду. Вот почему они всегда используют эти устаревшие установки user.get(prop).
ddekany
52

FYI, похоже, что синтаксис для получения значений изменился в соответствии с:

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
Чем отличается этот синтаксис?
Паркер
1
хороший ответ ;-) обратите внимание, что вам может потребоваться выполнить проверку на нуль при печати вашего значения, <#if h [key] ??> $ {key} = $ {h [key]}; </ # if>
Брэд Паркс,
1
Синтаксис не изменился. И то, [key]и другое .get(key)существует с давних времен. .get(key)не является чем-то особенным для FTL, он просто вызывает этот общедоступный метод Java. Но вы можете использовать его, только если FreeMarker был настроен для предоставления Mapметодов.
ddekany
Во время итерации я получаю методы (getClass, hashCode, equals, get, toString, class) ... однако я не вижу никаких свойств, таких как 'id', список которых я хочу получить. Есть предложения, как получить этот список свойств, не являющихся методами, из этого хеша? Мне нужно знать названия этих свойств. : |
MaxRocket 01
47

Начиная с 2.3.25, делайте это так:

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

Обратите внимание, что это также работает с нестроковыми ключами (в отличие от того map[key], что нужно было записать как map?api.get(key)тогда).

До 2.3.25 стандартным решением было:

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

Однако некоторые действительно старые интеграции FreeMarker используют странную конфигурацию, в которой общедоступные Mapметоды (например, getClass) отображаются как ключи. Это происходит, когда они используют чистый BeansWrapper(вместо DefaultObjectWrapper), simpleMapWrapperсвойство которого было оставлено false. Вам следует избегать такой настройки, поскольку она смешивает методы с реальными Mapзаписями. Но если вы столкнетесь с такой неудачной настройкой, способ избежать ситуации - использовать открытые методы Java, такие как user.entrySet(), user.get(key)и т.д., а не использовать конструкции языка шаблонов, такие как ?keysили user[key].

ддеканы
источник
Это прекрасно работает. Но я вижу ошибки в среде разработки springsource. Есть идеи, как это исправить? Спасибо
harshavmb
@harshavmb Какие ошибки? Возможно, он использует устаревший плагин FreeMarker, который поставляется со старой версией FreeMarker?
ddekany
Не думаю. Скачали последнюю версию из инструментов jboss. Я попробую на другой машине и дам знать.
harshavmb
@harshavmb Если вы введете что-то вроде ${x?nosuchthing}и наведете на него курсор, в отображаемом сообщении об ошибке будет указано, какую версию FreeMarker он использует. Так и должно быть 2.3.25-incubating.
ddekany
странно, я просто попробовал на Mac и не смог воспроизвести проблему. Проблема, похоже, только в моем vm. Я посмотрю на версию jar. Однако это просто ошибка в редакторе, но код был выполнен правильно.
harshavmb
12

Если вы используете BeansWrapper с уровнем раскрытия Expose.SAFE или Expose.ALL, то можно использовать стандартный Java-подход к итерации набора записей:

Например, в Freemarker (начиная с версии 2.3.19) будет работать следующее:

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

В Struts2, например, используется расширение BeanWrapper с установленным по умолчанию уровнем раскрытия, чтобы разрешить этот способ итерации.

Риз
источник
3
Вы действительно пробовали это? Потому что получил InvalidReferenceExceptionкогда пробовал, пока map?keysработал.
kdgregory
4
Это работает только при использовании в freemarker.ext.beans.BeansWrapperкачестве оболочки объекта. В противном случае Maps будет автоматически заключен в SimpleHashобъект, который не поддерживает #entrySet(). (см. freemarker.sourceforge.net/docs/api/freemarker/template/… )
Koraktor
Вы правы, и я обновил свой ответ, чтобы отразить ваш комментарий. Хорошо смотрится!
rees 06
1
Вышесказанное не будет работать так хорошо для хэша, созданного внутри FTL, особенно если вы используете преобразователь Spring Freemarker с BeanWrapper. Хэш, объявленный внутри Ftl-файла, не упакован и по-прежнему будет просто хешем, повторяемым с помощью ключей?.
skipy
1
Не используйте чистый BeansWrapper, по крайней мере, с его значениями по умолчанию, где simpleMapWrapperесть false. Это очень сбивает с толку, так как смешивает ключи с именами методов. Если вам нужно вызвать entrySet(), просто продолжайте использовать «чистую» обертку объекта, такую ​​как стандартная, и напишите, map?api.entrySet()если вам нужен доступ к Java API вместо ключей.
ddekany
2

Итерация объектов

Если ваши ключи карты являются объектом, а не строкой, вы можете выполнить итерацию с помощью Freemarker.

1) Преобразуйте карту в список в контроллере:

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

2) Итерируйте карту в шаблоне Freemarker, обращаясь к объекту в Ключе и Объекту в Значении:

<#list convertedMap as item>
    <#assign myObjectKey = item.getKey()/>
    <#assign myObjectValue = item.getValue()/>
    [...]
</#list>
Tk421
источник
1

Для полноты картины стоит упомянуть, что с недавнего времени в Freemarker появилась достойная обработка пустых коллекций.

Итак, наиболее удобный способ перебора карты:

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

Нет больше <#if ...>оберток.

Ондра Жижка
источник
Лучший ответ. Спасибо.
egemen
0

Вы можете использовать одинарные кавычки для доступа к ключу, который вы установили в своей программе Java.

Если вы установите карту в Java следующим образом

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

Затем вы можете получить доступ к членам 'hash' в Freemarker следующим образом:

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

Выход :

a
b
Ашиш Чабрия
источник
это показывает, как обращаться к отдельным ключам, но задается вопрос, как выполнять итерацию
Lambart