.user.nameVariable access
Read root or nested values from the render context and definitions.
JJT means Java JSON Template: a compact template language that compiles JSON-shaped documents into optimized expression trees for fast, repeatable rendering.
{
"definitions": [
{ "name": "Ada" },
{ "price": 420 },
{ "quantity": 3 },
{
"total": "{{ math:mul .price, .quantity }}"
}
],
"template": {
"customer": "{{ .name | string:trim }}",
"total": "{{ .total }}",
"tier": "{{ .total | gt 1000 ? 'priority' : 'standard' }}"
}
}
{ "customer": "Ada", "total": 1260, "tier": "priority" }
Add the library, compile a JSON-shaped script, then render it with a context map.
implementation("io.github.sibmaks.jjtemplate:jjtemplate:1.0.0")
<dependency>
<groupId>io.github.sibmaks.jjtemplate</groupId>
<artifactId>jjtemplate</artifactId>
<version>1.0.0</version>
<type>pom</type>
</dependency>
Pass definitions and a JSON-compatible template to TemplateScript.
Let the compiler fold constants and prepare the expression tree.
Supply request-specific values without recompiling the template.
var script = TemplateScript.builder()
.template(
Map.of(
"message", "{{ string:concat 'Hello, ', .name }}",
"active", "{{ .enabled }}"
)
)
.build();
var compiler = TemplateCompiler.getInstance();
var compiled = compiler.compile(script);
// Reuse compiled for every context.
var result = compiled.render(
Map.of(
"name", "Alice",
"enabled", true
)
);
JJT keeps JSON structure visible. Four interpolation forms cover substitution, conditional insertion, and spreading.
.user.nameRead root or nested values from the render context and definitions.
{{ expression }}Insert a typed result directly: boolean stays boolean, number stays number.
{{? expression }}Skip an object field or array item when the expression evaluates to null.
{{. expression }}Expand an object into an object or collection values into an array.
.value | string:trimPass the left-hand value into a function and compose transformations.
.ready ? 'yes' : 'no'Choose a value inline; branches may contain calls, pipes, and nested expressions.
.repository?.foo('bar')Return null when a property or matching method is absent; combine it with default for a fallback.
Definition keys for switch and range use the same {{ ... }} delimiters as other expressions. This keeps the source unambiguous.
Each example pairs a JJT document with the JSON value produced by the runtime.
Functions are grouped by namespace. Global logic functions deliberately omit a namespace.
Convert values with cast:str, cast:int, cast:float, or cast:boolean.
Compose and format strings, optionally with a locale.
Normalize case and remove leading or trailing whitespace.
Inspect string length, emptiness, and contained substrings.
Split, slice, and locate content, including negative substring indexes.
Replace literal targets or regular-expression matches.
Create a list or concatenate arrays and collections.
Inspect, select, and join list values.
Create key-value maps or collapse object properties.
Inspect map size, emptiness, and key presence.
Perform decimal-safe arithmetic, negation, and scale adjustment.
Create, parse, and format date and date-time values.
Build locale-aware number and currency formatters.
Compare values without a namespace.
Compose boolean logic; and and or short-circuit.
Return a fallback only when the primary value is null.
No functions match this filter.
Implement the public TemplateFunction contract, register the instance, and call it by namespace and name.
public final class ReverseTemplateFunction
implements TemplateFunction<String> {
public String invoke(List<Object> args) {
if (args.size() != 1) {
throw fail("exactly 1 argument required");
}
return reverse(args.get(0));
}
public String invoke(List<Object> args, Object pipeArg) {
if (!args.isEmpty()) {
throw fail("no arguments expected");
}
return reverse(pipeArg);
}
public String getNamespace() { return "custom"; }
public String getName() { return "reverse"; }
public boolean isDynamic() { return false; }
private String reverse(Object value) {
return value == null ? null
: new StringBuilder(value.toString()).reverse().toString();
}
}
var evaluation = TemplateEvaluationOptions.builder()
.functions(List.of(new ReverseTemplateFunction()))
.build();
var options = TemplateCompileOptions.builder()
.evaluationOptions(evaluation)
.build();
var compiler = TemplateCompiler.getInstance(options);"{{ custom:reverse .value }}"
"{{ .value | custom:reverse }}"Return false from isDynamic() only for deterministic, side-effect-free functions. JJTemplate may fold calls whose inputs are constant.
A few deliberate choices keep templates fast, testable, and unsurprising.
Build CompiledTemplate when configuration changes, then reuse it across render calls with request-specific contexts.
Custom functions should be thread-safe and side-effect free. Mark a function static only when identical inputs always produce identical outputs.
Use a stable namespace such as billing:tax. Names must be unique within a namespace, including built-ins.
Override isLazy() only for short-circuit behavior. Access the supplied arguments on demand instead of iterating or copying them.
Keep representative .jjt, context, and expected JSON fixtures together. Assert semantic values, not formatting.
Prefer small definitions and pipes over deeply nested expressions. The source should still read like the output it creates.
JJTemplate separates parsing, optimization, and evaluation so runtime work stays focused.
Tokenizes JJT expressions.
Builds expression syntax trees.
Creates executable nodes.
Folds and simplifies once.
Renders JSON-compatible values.
Supported lexer, parser, compiler API, runtime, exception, and function packages are checked against the published baseline during verification.
Read the policy →Ready to shape some JSON?