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
>

Reply via email to