samskivert / jmustache

A Java implementation of the Mustache templating language.
Other
834 stars 128 forks source link

Processing of nullable field with name 'value' #98

Closed jakubmisko closed 6 years ago

jakubmisko commented 6 years ago

Following template and data:

    @Test
    fun value() {

        class Data(val value: String?)

        println(Mustache.compiler()
                .compile("Values: {{#data.values}}{{#value}}{{value}}, {{/value}}{{/data.values}}")
                .execute(object : Any() {
                    val data = object : Any() {
                        val values = arrayOf(Data("1"), Data("2"), Data("3"), Data("4"))
                    }
                }))
    }

will produce output:

Values: [C@66c92293, [C@332796d3, [C@4f0100a7, [C@3cdf2c61, 

I would expect something like:

Values: 1, 2, 3, 4

Funny is that renaming property from 'value' to 'valuex' solved problem.

samskivert commented 6 years ago

You are using a somewhat error prone mechanism to avoid null values. With the template you've provided: inside {{#data.values}} the context is set to each Data instance in turn. So if you just used:

{{#data.values}}{{value}}, {{/data.values}}

then the {{value}}, fragment would be executed with the context bound to a Data instance, and {{value}} would call getValue on the Data object to obtain the String to be displayed. No problem, except that if the string was null, you would get null, in your output which I assume you don't want.

So instead you're using {{#value}}{{value}}, {{/value}} to start a section with the context bound to the String which will omit the section if the string is null and include it if it is non-null. But inside that section, the context is no longer bound to a Data, it is now bound to the String that Data.value referenced. So when you write {{value}} inside that string, it will look to see if String has a value member and output that. It just so happens that String does have a value member which is an array of characters. So JMustache displays that as requested (after calling toString on it).

That's why you see a bunch of char array toString output in your template.

When you change value to valuex, then JMustache will look up valuex with the String as its context, and not find any member named valuex, so then it will trigger the "check the next enclosing context" rule, which is the Data object, and in there it will find a valuex member, which is the String and it will use that. So this accomplishes what you want, but in a sort of accidental way.

The correct way to do what you want is this:

{{#data.values}}{{#value}}{{.}}, {{/value}}{{/data.values}}

because {{.}} means "use the entire context object as the output of this variable", which is exactly what you want when the context is bound to the String that you intend to print out. This works whether Data has a value or valuex member.