What a fun rabbit hole you've found!
Sometimes you will stumble upon an undocumented Freemarker context object. First step is to let our Documentation team know 🙂
Next, for the impatient and curious, is to figure out what is available to us via Freemarker. The challenge with freemarker, is that an object can be multiple datatypes, and we don't know which it is or which one we want. And freemarker will throw an exception and stop processing if you get it wrong.
I've gone down this path before, and this is imperfect, but this is the code I was using to help me figure out the objects available on the message context objects.
<#assign context = "env.context.message." />
<#list env.context.message as key,value>
<#assign val = (context+key)?eval!"" />
<#if val?is_enumerable>
<#assign val = "(sequence)" />
<#elseif val?is_method>
<#assign val = "(method)" />
<#elseif val?is_boolean>
<#assign val = val?c />
<#elseif val?is_hash />
<#assign val = "(extended hash)" />
<#else>
<#assign val = "(unknown)" />
</#if>
<#if key != "product">
${key?index} - ${key} - ${val}<p>
</#if>
</#list>
<ul>
<#list env.context.message?keys as key>
<li>${key?index} - ${key}
<#if key != "class">
<ul>
<#assign obj = (context+key)?eval!"" />
<#if !obj?is_string && !obj?is_enumerable>
<#list obj?keys as key>
${key}
</#list>
<#else>
${obj?string}
</#if>
</ul>
</#if>
</li>
</#list>
</ul>