Hi, Thanks for this clear explanation. I'm going to change my code accordingly.
Niels On Tue, Mar 25, 2025 at 10:37 PM Daniel Dekany <daniel.dek...@gmail.com> wrote: > The main reason is that unlike in the Java language, in the template > language there's no separate namespace for methods, and collection > elements. There's actually a setting in BeansWrapper to expose both, but > because of the common namespace that leads to some ambiguities and > surprises. The problem is the most obvious with Map-s, where myObj.name in > the template can mean both myObject.getName() or myObject.get("name") in > Java. And then, what should we return when you list myObject with #list? > Should we return a mix of Map keys, and Java method names? Things like > that. > > The secondary reason is hiding the technical details of the model. If one > day there's change in the data-model where something that was once an array > is now List, your templates won't break, because the template never seens > them as a List or an array, but as a "sequence", which is the native type > of the template language. By the way, in the rare case where you still want > to access the Java API, you can use ?api, like this: team?api.name > > As a historical perspective, FreeMarker originally didn't support > accessing properties, or calling methods. You had to put together the > data-model explicitly from the native types. > > On Tue, Mar 25, 2025 at 7:01 PM Niels Basjes <ni...@basjes.nl> wrote: > > > Hi, > > > > Thanks, this really helps. > > > > Just curious; What is the reason for making this distinction? > > Why does the DefaultObjectWrapper expose the getters of a Pojo, but it > does > > not expose them if it implements one of the mentioned collections? > > Is it possible to combine the two effects and do both (both making it > #list > > capable and getting the custom fields)? > > > > Niels > > > > On Mon, Mar 24, 2025 at 9:28 PM Daniel Dekany <daniel.dek...@gmail.com> > > wrote: > > > > > It's because if Team implements Set, List, Map, etc., then, by default, > > it > > > will only see the Collection/Map elements, and not the Java methods of > > the > > > object. > > > > > > Probably the cleanest/idiomatic solution is if Team is not a Set, but > > > instead has a method like Set<User> getUsers(). Regardless of > FreeMarker, > > > that's what I would do as a Java programmer. > > > > > > But if it must be a Set, you can force FreeMarker to ignore that like > > this: > > > > > > So you can do this: > > > > > > public static class CustomObjectWrapper extends > DefaultObjectWrapper > > { > > > public CustomObjectWrapper(Version incompatibleImprovements) { > > > super(incompatibleImprovements); > > > } > > > > > > @Override > > > public TemplateModel wrap(Object obj) throws > > TemplateModelException > > > { > > > if (obj instanceof Team) { > > > return new GenericObjectModel(obj, this); > > > } > > > return super.wrap(obj); > > > } > > > } > > > > > > and then where you create your Configuration singleton: > > > > > > configuration.setObjectWrapper(new > > > CustomObjectWrapper(Configuration.VERSION_2_3_35)); > > > > > > Although now that being a Set was ignored, team is not #list-able in > the > > > template (except with directly calling the Set API-s). > > > > > > > > > On Mon, Mar 24, 2025 at 12:00 PM Niels Basjes <ni...@basjes.nl> wrote: > > > > > > > Hi, > > > > > > > > I'm new to Freemarker and I'm looking for the right solution for > > > > something I ran into but have not been able to figure out. > > > > > > > > If I have a Java class like this: > > > > > > > > public static class Team { > > > > private final String name; > > > > public Team(String name) { > > > > this.name = name; > > > > } > > > > public String getName() { > > > > return name; > > > > } > > > > } > > > > > > > > If I then make a TemplateTest like this > > > > > > > > @Test > > > > public void testTeamName() throws Exception { > > > > Team team = new Team("Working"); > > > > addToDataModel("team", team); > > > > assertEquals("Working", team.getName()); > > > > assertOutput("${team.name}","Working"); > > > > } > > > > > > > > and it all works. > > > > If I change my Team to be a subclass of a well known collection (I > have > > > > tried TreeMap, TreeSet and ArrayList) then my .name attribute is no > > > longer > > > > available in a template. > > > > > > > > public static class TeamSet extends TreeSet<User> { > > > > > > > > > > > > What is the proper way to solve this? > > > > Perhaps a custom Object Wrapper? > > > > Or is this something I should report as a bug? > > > > > > > > Thanks. > > > > > > > > Niels Basjes > > > > nielsbas...@apache.org > > > > ni...@basjes.nl > > > > > > > > > > > > > -- > > > Best regards, > > > Daniel Dekany > > > > > > > > -- > Best regards, > Daniel Dekany >