JJTemplate: Lightweight JSON Template Engine for Java with AST Compilation

JJTemplate compiles JSON-compatible templates into optimized ASTs for fast rendering, preserving type fidelity and offering conditional insertion, spread, pipes, ternary, safe access, and namespaced functions.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
JJTemplate: Lightweight JSON Template Engine for Java with AST Compilation

Introduction

JJTemplate (Java JSON Template) is a lightweight template engine designed for minimal rendering latency while guaranteeing valid JSON output. It compiles templates into an optimized abstract syntax tree (AST) for fast execution. The engine accepts JSON-compatible input data and produces JSON-compatible results, keeping booleans as booleans and numbers as numbers.

Maven Dependencies

Two artifacts are required (version 1.0.2):

<dependency>
  <groupId>io.github.sibmaks.jjtemplate</groupId>
  <artifactId>jjtemplate-parser</artifactId>
  <version>1.0.2</version>
</dependency>
<dependency>
  <groupId>io.github.sibmaks.jjtemplate</groupId>
  <artifactId>jjtemplate-compiler</artifactId>
  <version>1.0.2</version>
</dependency>

Basic Usage

Define a template as a Map of keys to JJT expressions, compile it, then render with a data Map:

var script = TemplateScript.builder()
    .template(Map.of(
        "id", "{{ .id }}",
        "name", "{{ .name }}"
    ))
    .build();
var compiler = TemplateCompiler.getInstance();
var compiled = compiler.compile(script);
Map<String, Object> data = new HashMap<>();
data.put("id", 666);
data.put("name", "Pack_xg");
var result = compiled.render(data);
System.err.println(result);

Output:

{id=666, name=Pack_xg}

JJT Syntax

JJT keeps the JSON structure visible. Four interpolation forms cover substitution, conditional insertion, and spread.

Variable Access

Read root or nested values from the render context. Syntax:

.user.name

Substitution

Insert typed results directly; booleans stay booleans, numbers stay numbers. Syntax:

{{ expression }}

Conditional Insertion

Skip the object field or array item when the expression evaluates to null. Syntax:

{{? expression }}

Spread

Expand an object into an object, or expand collection values into an array. Syntax:

{{. expression }}

Pipes

Pass the left-hand value to a function and chain transformations. Syntax:

.value | string:trim

Ternary

Pick a value inline; branches may contain calls, pipes, and nested expressions. Syntax:

.ready ? 'yes' : 'no'

Safe Access

Return null when a property or matched method does not exist; combine with default for fallbacks. Syntax:

.repository?.foo('bar')

Syntax Examples

Conditional Insertion

Fields with null values are omitted from the output array.

var script = TemplateScript.builder()
    .template(List.of(
        "{{? .sex1 }}",
        "{{? .sex2 }}",
        "{{? .sex3 }}"
    ))
    .build();
var compiler = TemplateCompiler.getInstance();
var compiled = compiler.compile(script);
Map<String, Object> data = new HashMap<>();
data.put("sex1", "男");
data.put("sex2", "女");
data.put("sex3", null);
var result = compiled.render(data);
System.err.println(result);

Output:

[男, 女]

Spread

Spread merges the extra map into the root object.

var script = TemplateScript.builder()
    .template(Map.of(
        "name", "{{? .name }}",
        "{{. .extra }}", true
    ))
    .build();
var compiler = TemplateCompiler.getInstance();
var compiled = compiler.compile(script);
Map<String, Object> data = new HashMap<>();
data.put("name", "Pack_xg");
data.put("extra", Map.of("role", "admin"));
var result = compiled.render(data);
System.err.println(result);

Output:

{role=admin, name=Pack_xg}

Range (Iteration)

Define a reusable definition for each item, then reference it in the template.

Definition defintion = new Definition();
defintion.put("{{ rows range item,index of .items }}", Map.of(
    "position", "{{ .index }}",
    "value", "{{ .item }}"
));
var script = TemplateScript.builder()
    .definitions(List.of(defintion))
    .template("{{ .rows }}")
    .build();
var compiler = TemplateCompiler.getInstance();
var compiled = compiler.compile(script);
Map<String, Object> data = new HashMap<>();
data.put("items", List.of("A", "B", "C"));
var result = compiled.render(data);
System.err.println(result);

Output:

[
  {position=0, value=A},
  {position=1, value=B},
  {position=2, value=C}
]

Switch

Pattern-match a value with an optional else clause.

Definition defSwitch = new Definition();
defSwitch.put("{{ message switch .status }}", Map.of(
    "ok", "成功",
    "fail", "失败",
    "{{ else }}", "未知"
));
var script = TemplateScript.builder()
    .definitions(List.of(defSwitch))
    .template("{{ .message }}")
    .build();
var compiler = TemplateCompiler.getInstance();
var compiled = compiler.compile(script);
Map<String, Object> data = new HashMap<>();
data.put("status", "fail");
var result = compiled.render(data);
System.err.println(result);

Output:

失败

Safe Access with Default

Use safe navigation and default to provide a fallback when the path is missing.

var script = TemplateScript.builder()
    .template(Map.of(
        "name", "{{ default .user?.name, '未知' }}"
    ))
    .build();
var compiler = TemplateCompiler.getInstance();
var compiled = compiler.compile(script);
Map<String, Object> data = new HashMap<>();
data.put("user", Map.of("name", "Pack_xg"));
var result = compiled.render(data);
System.err.println(result);

Output: {name=Pack_xg} or {name=未知} when user is absent.

Using Functions

Functions are grouped by namespace; global logical functions omit the namespace. Example demonstrating string, list, map, date, and math functions:

var script = TemplateScript.builder()
    .template(Map.of(
        "name", "{{ .name?.substring(1) }}",
        "hobbySize", "{{ list:len .hobbies }}",
        "extras", "{{ map:new 'head', 'xxx', 'tail', 'ooo' }}",
        "birth", "{{ date:parse 'yyyy-MM-dd', .birth }}",
        "rate", "{{ math:div .left, .right }}"
    ))
    .build();
var compiler = TemplateCompiler.getInstance();
var compiled = compiler.compile(script);
Map<String, Object> data = new HashMap<>();
data.put("name", "Pack_xg");
data.put("hobbies", List.of("足球", "篮球", "排球"));
data.put("birth", "2026-10-05");
data.put("left", 460);
data.put("right", 100);
var result = compiled.render(data);
System.err.println(result);

Output:

{
  extras={head=xxx, tail=ooo},
  name=ack_xg,
  rate=4,
  birth=2026-10-05,
  hobbySize=3
}
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Javabackend developmenttemplate engineJSON templateAST compilationJJTemplate
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.