Freemarker iterujący po kluczach typu hashmap

87

Freemarker ma dwa typy danych kolekcji, listy i hashmapy. Czy istnieje sposób na iterację po kluczach hashmap, tak jak robimy to z listami?

Więc jeśli mam zmienną z danymi, powiedzmy:

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

Chciałbym wydrukować wszystkie właściwości użytkownika wraz z ich wartością. To jest nieprawidłowe, ale cel jest jasny:

<#list user.props() as prop>
  ${prop} = ${user.get(prop)}
</#list>
tzador
źródło

Odpowiedzi:

106

Edycja: nie używaj tego rozwiązania z FreeMarker 2.3.25 i nowszymi wersjami, a zwłaszcza nie .get(prop). Zobacz inne odpowiedzi.

Używasz wbudowanej funkcji klawiszy , np. To powinno działać:

<#list user?keys as prop>
    ${prop} = ${user.get(prop)}
</#list>  
skaffman
źródło
4
składnia jest inna w najnowszej wersji, co ilustruje link, który zamieściłem w mojej odpowiedzi. Zdaję sobie sprawę, że to stare pytanie, ale jest wysoko oceniane w Google.
Nick Spacek,
26
tylko uwaga - możesz użyć ${user[prop]}skrótu
Bozho
To jest wyciek wydajności: dla każdego klucza musi pobrać wartość. Iterowanie po entrySet () nie powoduje tego problemu.
Geoffrey De Smet
4
powinno być $ {user [prop]}
dns
Przy domyślnej konfiguracji user[prop]robót o ile propjest String(poza tym, czego potrzeba user?api.get(prop)obecnie), ale uwaga, niektóre ramy (jak Struts, wierzę) użyć teraz przestarzałej konfiguracji gdzie nazwy metod są zmieszane z Mapklawiszy, a więc wtedy, gdy wartość propsdzieje być nazwą metody w userobiekcie Java, otrzymasz metodę zamiast tego, co miałeś na myśli. Dlatego też w tych starszych konfiguracjach, których zawsze używają user.get(prop).
ddekany
52

FYI, wygląda na to, że składnia pobierania wartości zmieniła się zgodnie z:

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
źródło
2
Czym różni się ta składnia?
Parker
1
dobra odpowiedź ;-) zwróć uwagę, że podczas drukowania wartości może być konieczne sprawdzenie wartości null, <#if h [klucz] ??> $ {klucz} = $ {h [klucz]}; </ # if>
Brad Parks,
1
Składnia nie została zmieniona. Zarówno [key]i .get(key)istnieje od czasów starożytnych. .get(key)nie jest specjalnie dla FTL, po prostu wywołuje tę publiczną metodę Java. Ale możesz go używać tylko wtedy, gdy FreeMarker został skonfigurowany do ujawniania Mapmetod.
ddekany,
Podczas iteracji otrzymuję metody (getClass, hashCode, equals, get, toString, class) ... jednak nie widzę żadnych właściwości, takich jak `` id '', których chcę uzyskać listę. Jakieś sugestie, jak uzyskać listę właściwości niebędących metodami z tego skrótu? Muszę znać nazwy tych nieruchomości. : |
MaxRocket
47

Od 2.3.25 zrób to w ten sposób:

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

Zwróć uwagę, że działa to również z kluczami niebędącymi łańcuchami (w przeciwieństwie do tych map[key], które musiały być zapisane tak jak map?api.get(key)wtedy).

Przed 2.3.25 standardowym rozwiązaniem było:

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

Jednak niektóre naprawdę stare integracje FreeMarker używają dziwnej konfiguracji, w której Mapmetody publiczne (takie jak getClass) pojawiają się jako klucze. Dzieje się tak, ponieważ używają one czystego BeansWrapper(zamiast DefaultObjectWrapper), którego simpleMapWrapperwłaściwość została pozostawiona false. Powinieneś unikać takiej konfiguracji, ponieważ łączy ona metody z prawdziwymi Mapwpisami. Ale jeśli napotkasz takim niefortunnym konfiguracji, tak aby uniknąć sytuacji korzysta odsłonięte metod Java, takich jak user.entrySet(), user.get(key)itp, a nie przy użyciu konstrukcji językowych jak szablon ?keyslub user[key].

ddekany
źródło
To działa doskonale. Ale widzę błędy w springsource IDE. Masz pomysł, jak to naprawić? Dzięki
harshavmb
@harshavmb Jakie błędy? Czy może używa przestarzałej wtyczki FreeMarker, która jest dostarczana ze starą wersją FreeMarker?
ddekany
Nie myśl tak. Pobrałem najnowszą wersję z jboss tools. Spróbuję na innej maszynie i dam Ci znać.
harshavmb
@harshavmb Jeśli wpiszesz coś podobnego ${x?nosuchthing}i najedziesz na to kursorem, wyświetlony komunikat o błędzie powie, której wersji FreeMarker używa. Tak powinno być 2.3.25-incubating.
ddekany
dziwne, właśnie próbowałem na komputerze Mac i nie mogłem odtworzyć problemu. Wydaje się, że problem dotyczy tylko mojego vm. Rzucę okiem na wersję słoiczkową. Jednak to tylko błąd w edytorze, ale kod został wykonany poprawnie.
harshavmb
12

Jeśli używasz BeansWrapper z poziomem ekspozycji Expose.SAFE lub Expose.ALL, możesz zastosować standardowe podejście Java do iteracji zestawu wpisów:

Na przykład następujące elementy będą działać w Freemarker (od wersji co najmniej 2.3.19):

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

Na przykład w Struts2 używane jest rozszerzenie BeanWrapper z domyślnym poziomem ekspozycji, aby umożliwić ten sposób iteracji.

rees
źródło
3
Czy faktycznie próbowałeś tego? Ponieważ dostałem, InvalidReferenceExceptionkiedy próbowałem, podczas map?keyspracy.
kdgregory
4
Działa to tylko wtedy, gdy jest używane freemarker.ext.beans.BeansWrapperjako opakowanie obiektu. W przeciwnym razie Maps zostanie automatycznie zawinięty w SimpleHashobiekt, który nie obsługuje #entrySet(). (patrz freemarker.sourceforge.net/docs/api/freemarker/template/... )
Koraktor
Masz rację i zaktualizowałem moją odpowiedź, aby odzwierciedlić Twój komentarz. Dobry wygląd!
rees
1
Powyższe nie będzie działać zbyt dobrze dla skrótu utworzonego wewnątrz FTL, szczególnie jeśli używasz resolvera Spring Freemarker z BeanWrapper. Skrót zadeklarowany w pliku Ftl nie jest opakowany i nadal będzie po prostu hashem iterowalnym przy użyciu kluczy?
skipy
1
Nie używaj czystego BeansWrapper, przynajmniej nie z jego domyślnymi ustawieniami, gdzie simpleMapWrapperjest false. Robi się to bardzo zagmatwane, ponieważ miesza klucze z nazwami metod. Jeśli chcesz zadzwonić entrySet(), po prostu używaj „czystego” opakowania obiektu, takiego jak domyślny, i napisz, map?api.entrySet()jeśli chcesz uzyskać dostęp do Java API zamiast kluczy.
ddekany
2

Iterowanie obiektów

Jeśli twoje klucze mapy są obiektem, a nie łańcuchem, możesz iterować za pomocą Freemarkera.

1) Konwertuj mapę na listę w kontrolerze:

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

2) Iteruj mapę w szablonie Freemarker, uzyskując dostęp do obiektu w kluczu i obiektu w wartości:

<#list convertedMap as item>
    <#assign myObjectKey = item.getKey()/>
    <#assign myObjectValue = item.getValue()/>
    [...]
</#list>
Tk421
źródło
1

Dla kompletności warto wspomnieć, że od niedawna Freemarker przyzwoicie obsługuje puste kolekcje.

Najwygodniejszym sposobem iteracji mapy jest więc:

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

Żadnych więcej <#if ...>opakowań.

Ondra Žižka
źródło
Najlepsza odpowiedź. Dziękuję Ci.
egemen
0

Możesz użyć pojedynczego cudzysłowu, aby uzyskać dostęp do klucza ustawionego w programie Java.

Jeśli ustawisz mapę w Javie w ten sposób

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

Następnie możesz uzyskać dostęp do członków 'hash' w Freemarker w ten sposób -

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

Wynik :

a
b
Ashish Chhabria
źródło
który pokazuje, jak adresować poszczególne klucze, ale pytanie dotyczy iteracji
Lambart