Apache FreeMarker for transformation between data formats


In this post, we will learn how to use Apache FreeMarker for data format transformations

What is Apache FreeMarker?

Apache FreeMarker is a template engine: a Java library to generate text output (HTML web pages, e-mails, configuration files, XML, JSON, source code, etc.) based on templates and changing data. Templates are written in the FreeMarker Template Language (FTL), which is a simple, specialized language (not a full-blown programming language like PHP). Usually, a general-purpose programming language (like Java) is used to prepare the data (issue database queries, do business calculations). Then, Apache FreeMarker displays that prepared data using templates. In the template you are focusing on how to present the data, and outside the template you are focusing on what data to present.

If your project needs you to transform between data formats like XML to JSON or vice versa. Such transformations can be accomplished using FreeMarker

Apache FreeMarker for Data Transformations

XML TO JSON Transformation using FreeMarker

We will use SpringBoot project created using Spring Initilizer. https://start.spring.io/

FreeMarker Transformations – Project Structure

Firstly add dependencies to pom.xml

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-freemarker</artifactId>
    </dependency>
    <dependency>
        <groupId>com.googlecode.json-simple</groupId>
        <artifactId>json-simple</artifactId>
        <version>1.1</version>
    </dependency>

    <dependency>
        <groupId>no.api.freemarker</groupId>
        <artifactId>freemarker-java8</artifactId>
        <version>1.3.0</version>
    </dependency>

    <dependency>
        <groupId>org.everit.json</groupId>
        <artifactId>org.everit.json.schema</artifactId>
        <version>1.5.1</version>
    </dependency>

Add XML to transform in src/main/resources folder – test.xml

<?xml version="1.0" encoding="UTF-8"?>
<data>
    <employee>
        <id>30123</id>
        <name>Ben</name>
        <location>Toronto</location>
    </employee>
</data>

Add FTL Template in src/main/resources/templates folder – FTL file: xml2json.ftl

<#assign data = xml['child::node()']>
{
    "employee": {
        "id": ${data.employee.id},
        "name": "${data.employee.name}",
        "location": "${data.employee.location}"
    }
}

Create FmtManager to load and process template as below

package com.mvtechbytes.fmt;

import java.io.IOException;
import java.io.StringWriter;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Map;

import freemarker.cache.StringTemplateLoader;
import freemarker.ext.beans.BeansWrapperBuilder;
import freemarker.template.Configuration;
import freemarker.template.Template;

public class FmtManager {

    private Configuration freemarkerConfig;
    private static final String TEMPLATE_DIRECTORY = "src/main/resources/templates/";

    public FmtManager() {
        freemarkerConfig = new Configuration(Configuration.VERSION_2_3_23);
        freemarkerConfig.setTagSyntax(Configuration.ANGLE_BRACKET_TAG_SYNTAX);
        freemarkerConfig.setDefaultEncoding("UTF-8");
        freemarkerConfig.setNumberFormat("computer");
        freemarkerConfig.setObjectWrapper(new BeansWrapperBuilder(Configuration.VERSION_2_3_23).build());
        freemarkerConfig.setTemplateLoader(new StringTemplateLoader());
    }

    private Template loadTemplate(String templateName, String templatePath) {
        try {
            String templateContent = new String(Files.readAllBytes(Paths.get(templatePath)));
            ((StringTemplateLoader) freemarkerConfig.getTemplateLoader()).putTemplate(templateName, templateContent);
            return freemarkerConfig.getTemplate(templateName);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public String processTemplate(String templateName, Map<String, Object> data) {
        Template template = loadTemplate(templateName, TEMPLATE_DIRECTORY + templateName + ".ftl");
        try (StringWriter writer = new StringWriter()) {
            template.process(data, writer);
            return writer.toString();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

After adding all the code in the respective folders. Execution of use case can be done using below static method with FmtManager bean injected

public static void xmlToJson(FmtManager templateManager) throws Exception {

        String xmlString = new String(Files.readAllBytes(Paths.get("src/main/resources/test.xml")));
        NodeModel xmlNodeModel = NodeModel.parse(new InputSource(new StringReader(xmlString)));

        Map<String, Object> data = new HashMap<>();
        data.put("xml", xmlNodeModel);

        String json = templateManager.processTemplate("xml2json", data);

        System.out.println(json);
 }

Execution Log Output:

12:48:44.926 [main] DEBUG freemarker.cache - TemplateLoader.findTemplateSource("xml2json"): Found
12:48:44.929 [main] DEBUG freemarker.cache - Loading template for "xml2json"("en_US", UTF-8, parsed) from "xml2json"
{
"employee": {
"id": 101,
"name": "Vikas",
"location": "Toronto"
}
}

JSONTOXML Transformation using FreeMarker

Add JSON to transform in src/main/resources folder – test.json

{
  "data": {
    "employee": {
      "empid": 2012,
      "empname": "Virat",
      "location": "Hyderabad"
    }
  }
}

Add FTL Template in src/main/resources/templates folder – FTL file: json2xml.ftl

<#-- @ftlvariable name="JsonUtil" type="de.consol.jbl.util.JsonUtil" -->
<#assign body = JsonUtil.jsonToMap(input)>
<?xml version="1.0" encoding="UTF-8"?>
<data>
    <employee>
        <id>${body.data.employee.empid}</id>
        <name>${body.data.employee.empname}</name>
     	<location>${body.data.employee.location}</location>
    </employee>
</data>

Create FmtJSONUtil – This to convert json to Java object

package com.mvtechbytes.fmt;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

public class FmtJsonUtil {
    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    public static Map<String, Object> jsonToMap(String json) throws IOException {
        return OBJECT_MAPPER.readValue(json, new TypeReference<HashMap<String, Object>>(){});
    }
}

After adding all the code in the respective folders. Execution of use case can be done using below static method with FmtManager bean injected

private static void jsonToXml(FmtManager templateManager) throws IOException, TemplateModelException {
		 String input = new String(Files.readAllBytes(Paths.get("src/main/resources/test.json")));

	        Map<String, Object> data = new HashMap<>();
	        data.put("input", input);

	        TemplateHashModel staticModels = new BeansWrapperBuilder(Configuration.VERSION_2_3_23).build().getStaticModels();
	        data.put("JsonUtil", staticModels.get(FmtJsonUtil.class.getName()));

	        String output = templateManager.processTemplate("json2xml", data);

	        System.out.println(output);
	}

Execution Log Output:

<?xml version="1.0" encoding="UTF-8"?>
<data>
    <employee>
        <id>2012</id>
        <name>Virat</name>
     	<location>Hyderabad</location>
    </employee>
</data>

Full sourcecode is available in below github link

https://github.com/malliktalksjava/FreeMarkerTransformations

References:

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.