Working with JSON in Freemarker

Freemarker has some nice native support for working with JSON. And with the help of a few Liferay utility classes, it's very easy to even consume JSON web services from Freemarker. Note: you can view all of the example code on github: https://github.com/allen-ziegenfus/dev-playground/tree/master/freemarker

Converting JSON to Freemarker types

Freemarker hashes and sequences can be created from literal JSON strings as follows: 

Creating a normal hash

<#-- Freemarker hash from JSON literal -->
<# assign beer_map = { "name": "Imperial Stout", "description": "Tasty Stout Beer"} >
<div> ${beer_map.name} </div>
<div> ${beer_map.description} </div>

 

Output:

Imperial Stout
Tasty Stout Beer
 

Creating a sequence

<#-- Freemarker array from JSON literal -->
<# assign food_pairing_array = ["Salmon", "Pizza with Taleggio"] >
<# list food_pairing_array as food_pairing>
     <div> ${food_pairing} </div>
</# list>

 

Output: 

Salmon

Pizza with Taleggio

 

Creating a complex hash with an embedded sequence

<#-- Freemarker hash with array from JSON literal -->
<#assign complex_beer_map =
    {   
        "name": "Imperial Stout",
        "description": "Tasty Stout Beer",
        "food_pairing": [
            "Salmon",
            "Pizza with Taleggio"
        ]
    } >
<div> ${complex_beer_map.name} </div>
<div> ${complex_beer_map.description} </div>
<# list complex_beer_map.food_pairing as food_pairing>
     <div> ${food_pairing} </div>
</# list>

Output:

Imperial Stout
Tasty Stout Beer
Salmon
Pizza with Taleggio

 

But what if we have JSON in a String object? In this case there are a couple approaches:

Using the built-in ?eval

Using ?eval on a String that includes JSON will convert it like a literal to a Freemarker hash. As the name implies, Freemarker also evaluates any expressions in the String, which may or may not be what you want. For example, the reference ${name} is resolved, so the word "junk" appears in the output, even though it is not resolved through the assign tag (here I explicitly broke up the reference to show that ?eval is resolving it):

<h1>Simple deserialization with eval </h1>
<# assign beer_json_string = "{ \"name\": \"Imperial Stout\", \"description\": \"Tasty Stout Beer\"}">
<# assign beer_map = beer_json_string?eval>
<div> ${beer_map.name} </div>
<div> ${beer_map.description} </div>

<h1> Simple deserialization with eval, resolving references </h1>
<# assign name="junk">
<# assign beer_json_string_with_reference = "{ \"name\": \"Imperial Stout\", \"description\": \"Tasty " + "${" + "name} Stout Beer\"}">
<# assign beer_map_with_ref = beer_json_string_with_reference?eval>
<div> ${beer_map_with_ref.name} </div>
<div> ${beer_map_with_ref.description} </div>

 

Output:

Imperial Stout
Tasty Stout Beer
Imperial Stout
Tasty junk Stout Beer

 

Create a JSONObject with the Liferay jsonFactoryUtil

With the jsonFactoryUtil we can also pass a JSON string and get a JSONObject. This will not resolve any references (so no "junk"), but has the downside that we can't use expression language references with the resulting object. Instead we have to use the JSONObject methods like getString:

<h1>Deserialization with JSONObject </h1>
<# assign liferay_beer_map = jsonFactoryUtil.createJSONObject(beer_json_string_with_reference)>
<div> ${liferay_beer_map.getString( "name") } </div>
<div> ${liferay_beer_map.getString( "description") } </div>
 

 

Output: 

Imperial Stout
Tasty ${name} Stout Beer
 

Deserialization with jsonFactoryUtil

The deserialization methods of jsonFactoryUtil will return back an object that is apparently automagically mapped to a Freemarker hash. This makes referencing even nested JSON data easy and "natural", and you can also use the standard Freemarker operators to check for values or provide default values (!):

<h1>Deserialization with Freemarker Hash </h1>
<# assign beer_hashmap = jsonFactoryUtil.looseDeserializeSafe(beer_json_string_with_reference) >
<div> ${beer_hashmap.name} </div>
<div> ${beer_hashmap.description} </div>
<div> ${(beer_hashmap.notthere) ! } </div>
<# if beer_hashmap.name??>
${beer_hashmap.name} exists!
</# if>
 

 

Output: 

Imperial Stout
Tasty ${name} Stout Beer
 
IImperial Stout exists!
 

Converting Freemarker Objects to JSON

To go the other direction we can also use our friend jsonFactoryUtil. In this case I instantiate a Freemarker hash with a literal, and then output the JSON to the browser console.

<#assign complex_beer_map =
    {   
        "name": "Imperial Stout",
        "description": "Tasty Stout Beer",
        "food_pairing": [
            "Salmon",
            "Pizza with Taleggio"
        ]
    } >

<script>
     var beerMap = ${jsonFactoryUtil.l ooseSerializeDeep( complex_beer_map)};
     console. log( beerMap);
</script>

 

Bringing it all together

Finally, these techniques can be combined with using the httpUtil to pull JSON data from external services and create markup in Freemarker. Here is an example using the "Beer" api at https://punkapi.com/

 

<# assign response = httpUtil.URLtoString("http://api.punkapi.com/v2/beers/random")>

<# assign beers_info = jsonFactoryUtil.looseDeserializeSafe(response) >

<# list beers_info as beer_info>
     <h1> ${beer_info.name} </h1>
     <div> ${beer_info.description} </div>

     <h2>Ingredients </h2>

    <# list beer_info.ingredients?keys as ingredient_type>

         <h3> ${ingredient_type} </h3>
         <ul>
        <# if beer_info.ingredients[ingredient_type]?is_sequence>
        
            <# list beer_info.ingredients[ingredient_type] as ingredients>
                 <li> ${ingredients.name} </li>
            </# list>
            
        <# else>
             <li> ${beer_info.ingredients [ingredient_type] } </li>
        </# if>
         </ul>
    </# list>
</# list>
 
This produces the following output!
 

Dogma

Brewed with over ten different types of malt, and blended together with Scottish heather honey, it is a pantheon to the gods of intricacy and nuance; a beer that celebratesa confluence of ideas. Complex, indulgent and encapsulating, Dogma gives more than a cursory nod to history, to make you ponder the very nature of beer itself.

Ingredients

yeast

  • Wyeast 2007 - Pilsen Lager™

hops

  • Saaz
  • First Gold

malt

 

  • Pale Ale
  • Munich
  • Caramalt
  • Crystal
  • Dark Crystal
  • Wheat
  • Flaked Oats
  • Chocolate
  • Smoked
  • Amber
  • Brown