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.
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.nameSubstitution
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:trimTernary
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
}Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
