Discussion:
[FreeMarker-user] how to make freeMarker simply call toString?
Vitaliy Semochkin
2014-03-30 10:16:07 UTC
Permalink
Hello,

I want to configure Freemarker
in order to make it replace

every ${object}, ${object.someField}, ${object.someMethod()}
in my template with a toString value for the returned object.
Even if it is an array, list, map or collection.

How to configure freemarkert in order to do so?

Regards,
Vitaliy
Daniel Dekany
2014-03-30 14:35:03 UTC
Permalink
Hello,

The way it works currently is that if FreeMarker doesn't know how to
make string out of a value (an FTL sequence or FTL hash in your case),
it will check if the *wrapped* value (which is made by the
ObjectWrapper from the plain Java object) implements
TemplateScalarModel, and if it does, it will use that to generate the
text to print. So, everything is possible with a custom ObjectWrapper.

As of how to achieve this with an out-of-the box object wrapper...
BeansWrapper does this if you don't
beansWrapper.setSimpleMapWrapper(true) it, except for arrays, but the
toString output of arrays is useless anyway. However, I wouldn't
recommend setSimpleMapWrapper(false), because it mixes the method
names with the keys in the map, which can get quite confusing.

But maybe I can give a better answer (or an RFE...) if I know why is
this needed. So why?
--
Thanks,
Daniel Dekany
Post by Vitaliy Semochkin
Hello,
I want to configure Freemarker
in order to make it replace
every ${object}, ${object.someField}, ${object.someMethod()}
in my template with a toString value for the returned object.
Even if it is an array, list, map or collection.
How to configure freemarkert in order to do so?
Regards,
Vitaliy
Daniel Dekany
2014-03-30 21:59:40 UTC
Permalink
Hello Dánie,
You advise works great, thank you very much for the fast response!
As for your question, I need it because I do not know the types of
variablles that arrive to my template.
What will consume that template output? The toString() of arbitrary
objects is meaningless for users (non-developer human beings) for
sure. It's also useless for other programs, as it's not even possible
to write code that reliably extracts data from the toString() of
standard collections and maps, as they don't quote the items or
anything like that. It's only good for debugging. Do you need this for
debugging maybe?
When I have an expression ${someObject.someFieldOrPropertOrMethod} to be displayed
in case it returns a primitive i want it to be cast to String via
it's Wrapper class (Boolean, Float, etc)
in case it is an object that overrides Object's toString() I want
it to be displayed as a result of toString() execution.
in case it is a sequence that does not override toString() (e.g
array, list, set) I want it to be displayed as
[value1.toString,...,valueN.toString]
in case it is a Map that does not override toString I want it to be
displayed as [{key1.toString(),value1.toString()..., ]
Am I right that all I need to do, is to override BeansWrapper?
Yes, to ensure that all wrapped values that you want to print and
isn't a TemplateNumberModel or TemplateBooleanModel or
TemplateDateModel implements TemplateScalarModel, in the way that you
prefer, you will have to do that. Now, BeansWrapper is a quite complex
beast, so I'm not sure if it will be easy or not...
Once again, thank you very much for supporting this great project
and for a lighting fast response on Sunday
Regards,
Vitaliy
Hello,
The way it works currently is that if FreeMarker doesn't know how to
make string out of a value (an FTL sequence or FTL hash in your case),
it will check if the *wrapped* value (which is made by the
ObjectWrapper from the plain Java object) implements
TemplateScalarModel, and if it does, it will use that to generate the
text to print. So, everything is possible with a custom ObjectWrapper.
As of how to achieve this with an out-of-the box object wrapper...
BeansWrapper does this if you don't
beansWrapper.setSimpleMapWrapper(true) it, except for arrays, but the
toString output of arrays is useless anyway. However, I wouldn't
recommend setSimpleMapWrapper(false), because it mixes the method
names with the keys in the map, which can get quite confusing.
But maybe I can give a better answer (or an RFE...) if I know why is
this needed. So why?
--
Thanks,
Daniel Dekany
Post by Vitaliy Semochkin
Hello,
I want to configure Freemarker
in order to make it replace
every ${object}, ${object.someField}, ${object.someMethod()}
in my template with a toString value for the returned object.
Even if it is an array, list, map or collection.
How to configure freemarkert in order to do so?
Regards,
Vitaliy
--
Thanks,
Daniel Dekany
Vitaliy Semochkin
2014-03-31 07:34:34 UTC
Permalink
Thank you for fast reply, Daniel!
Post by Daniel Dekany
What will consume that template output? The toString() of arbitrary
objects is meaningless for users (non-developer human beings) for
sure. It's also useless for other programs, as it's not even possible
to write code that reliably extracts data from the toString() of
standard collections and maps, as they don't quote the items or
anything like that. It's only good for debugging. Do you need this for
debugging maybe?
I forgot to mention one case in my displaying algorithm (i marked it with
bold below)

When I have an expression ${someObject.someFieldOrPropertOrMethod} to be
displayed
in case it returns a primitive i want it to be cast to String via it's
Wrapper class (Boolean, Float, etc)
in case it is an object that overrides Object's toString() I want it to be
displayed as a result of toString() execution.
in case it is a sequence that does not override toString() (e.g array,
list, set) I want it to be displayed as
[value1.toString,...,valueN.toString]
in case it is a Map that does not override toString I want it to be
displayed as [{key1.toString(),value1.toString()..., ]
*in case it is an object that doesn't override toString() and none of the
above(not a collection, array,map) it will be displayed as "¿"*


I'm already digging in BeansWrapper
am I right that in I want objects that do not override toString methods
return a * "¿", *i need to provide a StringModel for them
instantiated as modelCache.getInstance(object, StringModel.FACTORY)?

Do I need to override public Object unwrap(TemplateModel model, Class hint)?

Regards,
Vitaliy
Post by Daniel Dekany
Hello Dánie,
You advise works great, thank you very much for the fast response!
As for your question, I need it because I do not know the types of
variablles that arrive to my template.
What will consume that template output? The toString() of arbitrary
objects is meaningless for users (non-developer human beings) for
sure. It's also useless for other programs, as it's not even possible
to write code that reliably extracts data from the toString() of
standard collections and maps, as they don't quote the items or
anything like that. It's only good for debugging. Do you need this for
debugging maybe?
When I have an expression ${someObject.someFieldOrPropertOrMethod} to be
displayed
in case it returns a primitive i want it to be cast to String via
it's Wrapper class (Boolean, Float, etc)
in case it is an object that overrides Object's toString() I want
it to be displayed as a result of toString() execution.
in case it is a sequence that does not override toString() (e.g
array, list, set) I want it to be displayed as
[value1.toString,...,valueN.toString]
in case it is a Map that does not override toString I want it to be
displayed as [{key1.toString(),value1.toString()..., ]
Am I right that all I need to do, is to override BeansWrapper?
Yes, to ensure that all wrapped values that you want to print and
isn't a TemplateNumberModel or TemplateBooleanModel or
TemplateDateModel implements TemplateScalarModel, in the way that you
prefer, you will have to do that. Now, BeansWrapper is a quite complex
beast, so I'm not sure if it will be easy or not...
Once again, thank you very much for supporting this great project
and for a lighting fast response on Sunday
Regards,
Vitaliy
Hello,
The way it works currently is that if FreeMarker doesn't know how to
make string out of a value (an FTL sequence or FTL hash in your case),
it will check if the *wrapped* value (which is made by the
ObjectWrapper from the plain Java object) implements
TemplateScalarModel, and if it does, it will use that to generate the
text to print. So, everything is possible with a custom ObjectWrapper.
As of how to achieve this with an out-of-the box object wrapper...
BeansWrapper does this if you don't
beansWrapper.setSimpleMapWrapper(true) it, except for arrays, but the
toString output of arrays is useless anyway. However, I wouldn't
recommend setSimpleMapWrapper(false), because it mixes the method
names with the keys in the map, which can get quite confusing.
But maybe I can give a better answer (or an RFE...) if I know why is
this needed. So why?
--
Thanks,
Daniel Dekany
Post by Vitaliy Semochkin
Hello,
I want to configure Freemarker
in order to make it replace
every ${object}, ${object.someField}, ${object.someMethod()}
in my template with a toString value for the returned object.
Even if it is an array, list, map or collection.
How to configure freemarkert in order to do so?
Regards,
Vitaliy
--
Thanks,
Daniel Dekany
Daniel Dekany
2014-03-31 21:59:18 UTC
Permalink
Post by Vitaliy Semochkin
Thank you for fast reply, Daniel!
I'm already digging in BeansWrapper
am I right that in I want objects that do not override toString
methods return a "¿", i need to provide a StringModel for them
instantiated as modelCache.getInstance(object, StringModel.FACTORY)?
Most certainly you should override BeansWrapper.getModelFactory. And
then, instead of MapModel, CollectionModel, etc, you had to create
sub-classes of those where getAsString (inherited from StringModel) is
overridden.

Of course, it's quite awkward that you can't just override getAsString
in StringModel, but that's just one of the many cases where
inheritance is not flexible enough. I wonder if
StringModel.gasAsString should call back into BeansWrapper so that
there's a single point where you can control its behavior. Then you
don't even need to override getModelFactory. I just wonder if it's
useful enough in general to introduce the (small) overhead that the
indirection means.
Post by Vitaliy Semochkin
Do I need to override public Object unwrap(TemplateModel model, Class hint)?
Nope.
--
Thanks,
Daniel Dekany
Vitaliy Semochkin
2014-03-30 19:38:35 UTC
Permalink
Hello Dánie,

You advise works great, thank you very much for the fast response!

As for your question, I need it because I do not know the types of
variablles that arrive to my template.

My only option to display it is to follow the rule:
When I have an expression ${someObject.someFieldOrPropertOrMethod} to be
displayed
in case it returns a primitive i want it to be cast to String via it's
Wrapper class (Boolean, Float, etc)
in case it is an object that overrides Object's toString() I want it to be
displayed as a result of toString() execution.
in case it is a sequence that does not override toString() (e.g array,
list, set) I want it to be displayed as
[value1.toString,...,valueN.toString]
in case it is a Map that does not override toString I want it to be
displayed as [{key1.toString(),value1.toString()..., ]

Am I right that all I need to do, is to override BeansWrapper?

Once again, thank you very much for supporting this great project and for a
lighting fast response on Sunday :-)

Regards,
Vitaliy
Post by Vitaliy Semochkin
Hello,
The way it works currently is that if FreeMarker doesn't know how to
make string out of a value (an FTL sequence or FTL hash in your case),
it will check if the *wrapped* value (which is made by the
ObjectWrapper from the plain Java object) implements
TemplateScalarModel, and if it does, it will use that to generate the
text to print. So, everything is possible with a custom ObjectWrapper.
As of how to achieve this with an out-of-the box object wrapper...
BeansWrapper does this if you don't
beansWrapper.setSimpleMapWrapper(true) it, except for arrays, but the
toString output of arrays is useless anyway. However, I wouldn't
recommend setSimpleMapWrapper(false), because it mixes the method
names with the keys in the map, which can get quite confusing.
But maybe I can give a better answer (or an RFE...) if I know why is
this needed. So why?
--
Thanks,
Daniel Dekany
Post by Vitaliy Semochkin
Hello,
I want to configure Freemarker
in order to make it replace
every ${object}, ${object.someField}, ${object.someMethod()}
in my template with a toString value for the returned object.
Even if it is an array, list, map or collection.
How to configure freemarkert in order to do so?
Regards,
Vitaliy
Loading...