Overview
JSON template rendering allows dynamic generation of JSON documents using templates with placeholders, loops, conditionals, and other logic. This is useful for generating API responses, configuration files, and data exchange formats.
Common Approaches
- String-based templates (simple but error-prone)
- Library-specific templates (Jackson, Gson, etc.)
- Domain-Specific Languages (DSL) for JSON building
- Template engines (Handlebars, Mustache, etc.)
Basic String-Based Template Rendering
1. Simple String Replacement
import java.util.Map;
import java.util.regex.Pattern;
public class SimpleJsonTemplate {
public static String renderTemplate(String template, Map<String, Object> context) {
String result = template;
for (Map.Entry<String, Object> entry : context.entrySet()) {
String placeholder = "\\$\\{" + entry.getKey() + "\\}";
String value = escapeJson(entry.getValue().toString());
result = result.replaceAll(placeholder, value);
}
return result;
}
private static String escapeJson(String input) {
return input.replace("\\", "\\\\")
.replace("\"", "\\\"")
.replace("\b", "\\b")
.replace("\f", "\\f")
.replace("\n", "\\n")
.replace("\r", "\\r")
.replace("\t", "\\t");
}
public static void main(String[] args) {
String template = """
{
"user": {
"id": "${userId}",
"name": "${userName}",
"email": "${email}"
},
"timestamp": "${timestamp}"
}
""";
Map<String, Object> context = Map.of(
"userId", "12345",
"userName", "John Doe",
"email", "[email protected]",
"timestamp", "2024-01-15T10:30:00Z"
);
String json = renderTemplate(template, context);
System.out.println(json);
}
}
2. Advanced Template Engine
import java.util.*;
import java.util.regex.*;
public class AdvancedJsonTemplateEngine {
private static final Pattern PLACEHOLDER_PATTERN =
Pattern.compile("\\$\\{([^}]+)\\}");
private static final Pattern LOOP_PATTERN =
Pattern.compile("\\{\\{\\s*#each\\s+([^}]+)\\}\\}([\\s\\S]*?)\\{\\{\\/each\\}\\}");
private static final Pattern CONDITIONAL_PATTERN =
Pattern.compile("\\{\\{\\s*#if\\s+([^}]+)\\}\\}([\\s\\S]*?)\\{\\{\\/if\\}\\}");
public String render(String template, Map<String, Object> context) {
String result = template;
// Handle loops first
result = processLoops(result, context);
// Handle conditionals
result = processConditionals(result, context);
// Handle simple placeholders
result = processPlaceholders(result, context);
return result;
}
private String processLoops(String template, Map<String, Object> context) {
Matcher matcher = LOOP_PATTERN.matcher(template);
StringBuffer result = new StringBuffer();
while (matcher.find()) {
String listKey = matcher.group(1).trim();
String loopTemplate = matcher.group(2);
Object value = getNestedValue(context, listKey);
if (value instanceof List) {
List<?> items = (List<?>) value;
StringBuilder loopResult = new StringBuilder();
for (Object item : items) {
if (item instanceof Map) {
// Process the loop template with current item context
Map<String, Object> itemContext = (Map<String, Object>) item;
String itemResult = processPlaceholders(loopTemplate, itemContext);
loopResult.append(itemResult);
} else {
// Simple value - replace ${this} placeholder
String itemResult = loopTemplate.replace("${this}",
escapeJson(item.toString()));
loopResult.append(itemResult);
}
}
matcher.appendReplacement(result, Matcher.quoteReplacement(loopResult.toString()));
} else {
// If not a list, remove the loop content
matcher.appendReplacement(result, "");
}
}
matcher.appendTail(result);
return result.toString();
}
private String processConditionals(String template, Map<String, Object> context) {
Matcher matcher = CONDITIONAL_PATTERN.matcher(template);
StringBuffer result = new StringBuffer();
while (matcher.find()) {
String conditionKey = matcher.group(1).trim();
String conditionalTemplate = matcher.group(2);
Object value = getNestedValue(context, conditionKey);
boolean condition = evaluateCondition(value);
if (condition) {
String processed = processPlaceholders(conditionalTemplate, context);
matcher.appendReplacement(result, Matcher.quoteReplacement(processed));
} else {
matcher.appendReplacement(result, "");
}
}
matcher.appendTail(result);
return result.toString();
}
private String processPlaceholders(String template, Map<String, Object> context) {
Matcher matcher = PLACEHOLDER_PATTERN.matcher(template);
StringBuffer result = new StringBuffer();
while (matcher.find()) {
String key = matcher.group(1).trim();
Object value = getNestedValue(context, key);
if (value != null) {
String replacement = escapeJson(value.toString());
matcher.appendReplacement(result, Matcher.quoteReplacement(replacement));
} else {
// Leave placeholder unchanged if value not found
matcher.appendReplacement(result, Matcher.quoteReplacement(matcher.group()));
}
}
matcher.appendTail(result);
return result.toString();
}
private Object getNestedValue(Map<String, Object> context, String key) {
String[] parts = key.split("\\.");
Object current = context;
for (String part : parts) {
if (current instanceof Map) {
current = ((Map<String, Object>) current).get(part);
} else {
return null;
}
}
return current;
}
private boolean evaluateCondition(Object value) {
if (value == null) return false;
if (value instanceof Boolean) return (Boolean) value;
if (value instanceof String) return !((String) value).isEmpty();
if (value instanceof Collection) return !((Collection<?>) value).isEmpty();
if (value instanceof Map) return !((Map<?, ?>) value).isEmpty();
return true;
}
private String escapeJson(String input) {
if (input == null) return "";
return input.replace("\\", "\\\\")
.replace("\"", "\\\"")
.replace("\b", "\\b")
.replace("\f", "\\f")
.replace("\n", "\\n")
.replace("\r", "\\r")
.replace("\t", "\\t");
}
public static void main(String[] args) {
AdvancedJsonTemplateEngine engine = new AdvancedJsonTemplateEngine();
String template = """
{
"users": {{#each users}}
{
"id": "${id}",
"name": "${name}",
"active": ${active},
"roles": [{{#each roles}}"${this}"{{#unless @last}},{{/unless}}{{/each}}]
}{{#unless @last}},{{/unless}}{{/each}}
}
""";
Map<String, Object> context = Map.of(
"users", List.of(
Map.of("id", "1", "name", "John Doe", "active", true,
"roles", List.of("admin", "user")),
Map.of("id", "2", "name", "Jane Smith", "active", false,
"roles", List.of("user"))
)
);
String json = engine.render(template, context);
System.out.println(json);
}
}
Library-Based Approaches
1. Jackson-Based Template Rendering
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.*;
import java.util.*;
import java.util.function.Function;
public class JacksonTemplateEngine {
private final ObjectMapper objectMapper;
public JacksonTemplateEngine() {
this.objectMapper = new ObjectMapper();
}
public JsonNode renderTemplate(JsonNode template, Map<String, Object> context) {
return processNode(template, context);
}
public String renderTemplate(String jsonTemplate, Map<String, Object> context) {
try {
JsonNode template = objectMapper.readTree(jsonTemplate);
JsonNode result = renderTemplate(template, context);
return objectMapper.writeValueAsString(result);
} catch (Exception e) {
throw new RuntimeException("Failed to render template", e);
}
}
private JsonNode processNode(JsonNode node, Map<String, Object> context) {
if (node.isTextual()) {
String text = node.asText();
if (text.startsWith("${") && text.endsWith("}")) {
// Handle placeholder
String key = text.substring(2, text.length() - 1);
return resolveValue(key, context);
} else if (text.contains("${")) {
// Handle string with embedded placeholders
return new TextNode(processStringWithPlaceholders(text, context));
}
} else if (node.isArray()) {
ArrayNode result = objectMapper.createArrayNode();
for (JsonNode element : node) {
result.add(processNode(element, context));
}
return result;
} else if (node.isObject()) {
ObjectNode result = objectMapper.createObjectNode();
node.fields().forEachRemaining(entry -> {
result.set(entry.getKey(), processNode(entry.getValue(), context));
});
return result;
}
return node;
}
private JsonNode resolveValue(String key, Map<String, Object> context) {
Object value = getNestedValue(context, key);
if (value == null) {
return NullNode.getInstance();
}
if (value instanceof String) {
return new TextNode((String) value);
} else if (value instanceof Number) {
if (value instanceof Integer) {
return new IntNode((Integer) value);
} else if (value instanceof Long) {
return new LongNode((Long) value);
} else if (value instanceof Double) {
return new DoubleNode((Double) value);
}
} else if (value instanceof Boolean) {
return BooleanNode.valueOf((Boolean) value);
} else if (value instanceof List) {
ArrayNode array = objectMapper.createArrayNode();
for (Object item : (List<?>) value) {
array.add(convertToJsonNode(item));
}
return array;
} else if (value instanceof Map) {
ObjectNode object = objectMapper.createObjectNode();
((Map<String, Object>) value).forEach((k, v) -> {
object.set(k, convertToJsonNode(v));
});
return object;
}
return new TextNode(value.toString());
}
private JsonNode convertToJsonNode(Object value) {
try {
return objectMapper.valueToTree(value);
} catch (IllegalArgumentException e) {
return new TextNode(value.toString());
}
}
private String processStringWithPlaceholders(String text, Map<String, Object> context) {
Pattern pattern = Pattern.compile("\\$\\{([^}]+)\\}");
Matcher matcher = pattern.matcher(text);
StringBuffer result = new StringBuffer();
while (matcher.find()) {
String key = matcher.group(1);
Object value = getNestedValue(context, key);
String replacement = value != null ? value.toString() : "";
matcher.appendReplacement(result, Matcher.quoteReplacement(replacement));
}
matcher.appendTail(result);
return result.toString();
}
private Object getNestedValue(Map<String, Object> context, String key) {
String[] parts = key.split("\\.");
Object current = context;
for (String part : parts) {
if (current instanceof Map) {
current = ((Map<String, Object>) current).get(part);
} else {
return null;
}
if (current == null) break;
}
return current;
}
// Advanced template with custom functions
public static class AdvancedJacksonTemplateEngine extends JacksonTemplateEngine {
private final Map<String, Function<List<Object>, Object>> functions = new HashMap<>();
public AdvancedJacksonTemplateEngine() {
registerDefaultFunctions();
}
public void registerFunction(String name, Function<List<Object>, Object> function) {
functions.put(name, function);
}
private void registerDefaultFunctions() {
registerFunction("upper", args -> {
if (args.size() != 1) throw new IllegalArgumentException("upper expects 1 argument");
return args.get(0).toString().toUpperCase();
});
registerFunction("lower", args -> {
if (args.size() != 1) throw new IllegalArgumentException("lower expects 1 argument");
return args.get(0).toString().toLowerCase();
});
registerFunction("length", args -> {
if (args.size() != 1) throw new IllegalArgumentException("length expects 1 argument");
Object arg = args.get(0);
if (arg instanceof Collection) return ((Collection<?>) arg).size();
if (arg instanceof Map) return ((Map<?, ?>) arg).size();
return arg.toString().length();
});
}
@Override
protected JsonNode resolveValue(String key, Map<String, Object> context) {
// Check if it's a function call
if (key.contains("(") && key.endsWith(")")) {
int parenIndex = key.indexOf('(');
String functionName = key.substring(0, parenIndex);
String argsString = key.substring(parenIndex + 1, key.length() - 1);
Function<List<Object>, Object> function = functions.get(functionName);
if (function != null) {
List<Object> arguments = parseArguments(argsString, context);
Object result = function.apply(arguments);
return convertToJsonNode(result);
}
}
return super.resolveValue(key, context);
}
private List<Object> parseArguments(String argsString, Map<String, Object> context) {
List<Object> arguments = new ArrayList<>();
// Simple argument parsing - split by commas and trim
String[] argParts = argsString.split(",");
for (String arg : argParts) {
arg = arg.trim();
if (arg.startsWith("'") && arg.endsWith("'")) {
// String literal
arguments.add(arg.substring(1, arg.length() - 1));
} else if (arg.startsWith("${") && arg.endsWith("}")) {
// Reference to context
String key = arg.substring(2, arg.length() - 1);
arguments.add(getNestedValue(context, key));
} else {
// Try to parse as number or use as string
try {
arguments.add(Integer.parseInt(arg));
} catch (NumberFormatException e) {
try {
arguments.add(Double.parseDouble(arg));
} catch (NumberFormatException e2) {
arguments.add(arg);
}
}
}
}
return arguments;
}
}
public static void main(String[] args) {
JacksonTemplateEngine engine = new JacksonTemplateEngine();
String template = """
{
"user": {
"id": "${user.id}",
"name": "${user.name}",
"email": "${user.email}",
"profile": {
"age": "${user.profile.age}",
"city": "${user.profile.city}"
}
},
"settings": {
"notifications": "${user.settings.notifications}",
"language": "${user.settings.language}"
}
}
""";
Map<String, Object> context = Map.of(
"user", Map.of(
"id", 12345,
"name", "John Doe",
"email", "[email protected]",
"profile", Map.of(
"age", 30,
"city", "New York"
),
"settings", Map.of(
"notifications", true,
"language", "en"
)
)
);
String json = engine.renderTemplate(template, context);
System.out.println(json);
// Test advanced engine with functions
AdvancedJacksonTemplateEngine advancedEngine = new AdvancedJacksonTemplateEngine();
String advancedTemplate = """
{
"user": {
"name": "${upper(user.name)}",
"name_lower": "${lower(user.name)}",
"name_length": "${length(user.name)}"
}
}
""";
String advancedJson = advancedEngine.renderTemplate(advancedTemplate, context);
System.out.println(advancedJson);
}
}
2. Gson-Based Template Rendering
import com.google.gson.*;
import java.util.*;
import java.util.regex.*;
public class GsonTemplateEngine {
private final Gson gson;
public GsonTemplateEngine() {
this.gson = new GsonBuilder().setPrettyPrinting().create();
}
public JsonElement renderTemplate(String jsonTemplate, Map<String, Object> context) {
JsonElement template = JsonParser.parseString(jsonTemplate);
return processElement(template, context);
}
public String renderTemplateToString(String jsonTemplate, Map<String, Object> context) {
JsonElement result = renderTemplate(jsonTemplate, context);
return gson.toJson(result);
}
private JsonElement processElement(JsonElement element, Map<String, Object> context) {
if (element.isJsonPrimitive()) {
JsonPrimitive primitive = element.getAsJsonPrimitive();
if (primitive.isString()) {
String value = primitive.getAsString();
return processStringValue(value, context);
}
return primitive;
} else if (element.isJsonArray()) {
JsonArray array = element.getAsJsonArray();
JsonArray result = new JsonArray();
for (JsonElement item : array) {
result.add(processElement(item, context));
}
return result;
} else if (element.isJsonObject()) {
JsonObject object = element.getAsJsonObject();
JsonObject result = new JsonObject();
for (Map.Entry<String, JsonElement> entry : object.entrySet()) {
result.add(entry.getKey(), processElement(entry.getValue(), context));
}
return result;
}
return element;
}
private JsonElement processStringValue(String value, Map<String, Object> context) {
if (value.startsWith("${") && value.endsWith("}")) {
// Single placeholder
String key = value.substring(2, value.length() - 1);
Object resolved = getNestedValue(context, key);
return convertToJsonElement(resolved);
} else if (value.contains("${")) {
// String with embedded placeholders
Pattern pattern = Pattern.compile("\\$\\{([^}]+)\\}");
Matcher matcher = pattern.matcher(value);
StringBuffer result = new StringBuffer();
while (matcher.find()) {
String key = matcher.group(1);
Object replacement = getNestedValue(context, key);
matcher.appendReplacement(result,
replacement != null ? Matcher.quoteReplacement(replacement.toString()) : "");
}
matcher.appendTail(result);
return new JsonPrimitive(result.toString());
}
return new JsonPrimitive(value);
}
private Object getNestedValue(Map<String, Object> context, String key) {
String[] parts = key.split("\\.");
Object current = context;
for (String part : parts) {
if (current instanceof Map) {
current = ((Map<String, Object>) current).get(part);
} else {
return null;
}
if (current == null) break;
}
return current;
}
private JsonElement convertToJsonElement(Object value) {
if (value == null) {
return JsonNull.INSTANCE;
} else if (value instanceof String) {
return new JsonPrimitive((String) value);
} else if (value instanceof Number) {
return new JsonPrimitive((Number) value);
} else if (value instanceof Boolean) {
return new JsonPrimitive((Boolean) value);
} else if (value instanceof List) {
JsonArray array = new JsonArray();
for (Object item : (List<?>) value) {
array.add(convertToJsonElement(item));
}
return array;
} else if (value instanceof Map) {
JsonObject object = new JsonObject();
((Map<String, Object>) value).forEach((k, v) -> {
object.add(k, convertToJsonElement(v));
});
return object;
} else {
return new JsonPrimitive(value.toString());
}
}
public static void main(String[] args) {
GsonTemplateEngine engine = new GsonTemplateEngine();
String template = """
{
"api_response": {
"status": "success",
"data": {
"user": {
"id": "${user.id}",
"username": "${user.username}",
"full_name": "Welcome, ${user.firstName} ${user.lastName}!",
"preferences": ${user.preferences}
}
},
"pagination": {
"page": "${pagination.page}",
"total_pages": "${pagination.totalPages}",
"has_more": "${pagination.hasMore}"
}
}
}
""";
Map<String, Object> context = new HashMap<>();
context.put("user", Map.of(
"id", 123,
"username", "johndoe",
"firstName", "John",
"lastName", "Doe",
"preferences", Map.of(
"theme", "dark",
"notifications", true,
"language", "en"
)
));
context.put("pagination", Map.of(
"page", 1,
"totalPages", 5,
"hasMore", true
));
String result = engine.renderTemplateToString(template, context);
System.out.println(result);
}
}
Practical Examples
Example 1: API Response Template System
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.JsonNode;
import java.util.*;
public class ApiResponseTemplateSystem {
private final ObjectMapper objectMapper;
private final Map<String, String> templates;
private final JacksonTemplateEngine templateEngine;
public ApiResponseTemplateSystem() {
this.objectMapper = new ObjectMapper();
this.templates = new HashMap<>();
this.templateEngine = new JacksonTemplateEngine();
loadDefaultTemplates();
}
public void registerTemplate(String templateName, String template) {
templates.put(templateName, template);
}
public String renderResponse(String templateName, Map<String, Object> data) {
String template = templates.get(templateName);
if (template == null) {
throw new IllegalArgumentException("Template not found: " + templateName);
}
return templateEngine.renderTemplate(template, data);
}
public String renderResponse(String templateName, Object data) {
Map<String, Object> context = objectMapper.convertValue(data, Map.class);
return renderResponse(templateName, context);
}
private void loadDefaultTemplates() {
// User-related templates
registerTemplate("user_profile", """
{
"success": true,
"data": {
"user": {
"id": "${user.id}",
"username": "${user.username}",
"email": "${user.email}",
"profile": {
"firstName": "${user.profile.firstName}",
"lastName": "${user.profile.lastName}",
"avatar": "${user.profile.avatarUrl}",
"bio": "${user.profile.bio}"
},
"statistics": {
"posts": "${user.stats.postCount}",
"followers": "${user.stats.followerCount}",
"following": "${user.stats.followingCount}"
}
}
},
"metadata": {
"timestamp": "${timestamp}",
"version": "${apiVersion}"
}
}
""");
registerTemplate("user_list", """
{
"success": true,
"data": {
"users": [
{{#each users}}
{
"id": "${id}",
"username": "${username}",
"email": "${email}",
"status": "${status}"
}{{#unless @last}},{{/unless}}
{{/each}}
]
},
"pagination": {
"page": "${pagination.page}",
"pageSize": "${pagination.pageSize}",
"total": "${pagination.total}",
"totalPages": "${pagination.totalPages}"
},
"metadata": {
"timestamp": "${timestamp}",
"version": "${apiVersion}"
}
}
""");
registerTemplate("error", """
{
"success": false,
"error": {
"code": "${error.code}",
"message": "${error.message}",
"details": "${error.details}"
},
"metadata": {
"timestamp": "${timestamp}",
"requestId": "${requestId}",
"version": "${apiVersion}"
}
}
""");
}
// Domain classes
public static class User {
private final Long id;
private final String username;
private final String email;
private final Profile profile;
private final UserStats stats;
public User(Long id, String username, String email, Profile profile, UserStats stats) {
this.id = id;
this.username = username;
this.email = email;
this.profile = profile;
this.stats = stats;
}
// Getters
public Long getId() { return id; }
public String getUsername() { return username; }
public String getEmail() { return email; }
public Profile getProfile() { return profile; }
public UserStats getStats() { return stats; }
}
public static class Profile {
private final String firstName;
private final String lastName;
private final String avatarUrl;
private final String bio;
public Profile(String firstName, String lastName, String avatarUrl, String bio) {
this.firstName = firstName;
this.lastName = lastName;
this.avatarUrl = avatarUrl;
this.bio = bio;
}
// Getters
public String getFirstName() { return firstName; }
public String getLastName() { return lastName; }
public String getAvatarUrl() { return avatarUrl; }
public String getBio() { return bio; }
}
public static class UserStats {
private final int postCount;
private final int followerCount;
private final int followingCount;
public UserStats(int postCount, int followerCount, int followingCount) {
this.postCount = postCount;
this.followerCount = followerCount;
this.followingCount = followingCount;
}
// Getters
public int getPostCount() { return postCount; }
public int getFollowerCount() { return followerCount; }
public int getFollowingCount() { return followingCount; }
}
public static class Pagination {
private final int page;
private final int pageSize;
private final long total;
private final int totalPages;
public Pagination(int page, int pageSize, long total, int totalPages) {
this.page = page;
this.pageSize = pageSize;
this.total = total;
this.totalPages = totalPages;
}
// Getters
public int getPage() { return page; }
public int getPageSize() { return pageSize; }
public long getTotal() { return total; }
public int getTotalPages() { return totalPages; }
}
public static void main(String[] args) {
ApiResponseTemplateSystem templateSystem = new ApiResponseTemplateSystem();
// Create sample user data
User user = new User(
12345L,
"johndoe",
"[email protected]",
new Profile("John", "Doe", "https://example.com/avatar.jpg", "Software developer"),
new UserStats(42, 150, 89)
);
// Prepare context
Map<String, Object> context = new HashMap<>();
context.put("user", user);
context.put("timestamp", new Date().toString());
context.put("apiVersion", "1.0.0");
// Render user profile
String profileJson = templateSystem.renderResponse("user_profile", context);
System.out.println("User Profile:");
System.out.println(profileJson);
// Render error
Map<String, Object> errorContext = new HashMap<>();
errorContext.put("error", Map.of(
"code", "USER_NOT_FOUND",
"message", "The requested user was not found",
"details", "User ID 99999 does not exist"
));
errorContext.put("timestamp", new Date().toString());
errorContext.put("requestId", "req-12345");
errorContext.put("apiVersion", "1.0.0");
String errorJson = templateSystem.renderResponse("error", errorContext);
System.out.println("\nError Response:");
System.out.println(errorJson);
}
}
Example 2: Configuration File Template System
import java.util.*;
import java.nio.file.*;
public class ConfigurationTemplateSystem {
private final AdvancedJsonTemplateEngine templateEngine;
private final Map<String, String> environmentConfigs;
public ConfigurationTemplateSystem() {
this.templateEngine = new AdvancedJsonTemplateEngine();
this.environmentConfigs = new HashMap<>();
loadEnvironmentConfigs();
}
public String generateConfig(String environment, Map<String, Object> overrides) {
String template = environmentConfigs.get(environment);
if (template == null) {
throw new IllegalArgumentException("Unknown environment: " + environment);
}
Map<String, Object> context = createBaseContext(environment);
if (overrides != null) {
context.putAll(overrides);
}
return templateEngine.render(template, context);
}
public void saveConfigToFile(String environment, Map<String, Object> overrides, String filePath) {
try {
String config = generateConfig(environment, overrides);
Files.writeString(Path.of(filePath), config);
System.out.println("Configuration saved to: " + filePath);
} catch (Exception e) {
throw new RuntimeException("Failed to save configuration", e);
}
}
private Map<String, Object> createBaseContext(String environment) {
Map<String, Object> context = new HashMap<>();
// Database configurations
context.put("database", Map.of(
"dev", Map.of(
"url", "jdbc:postgresql://localhost:5432/app_dev",
"username", "dev_user",
"password", "dev_pass",
"poolSize", 5
),
"test", Map.of(
"url", "jdbc:postgresql://localhost:5432/app_test",
"username", "test_user",
"password", "test_pass",
"poolSize", 10
),
"prod", Map.of(
"url", "jdbc:postgresql://db-prod.cluster:5432/app_prod",
"username", "prod_user",
"password", "${DB_PASSWORD}",
"poolSize", 50
)
));
// Redis configurations
context.put("redis", Map.of(
"dev", Map.of("host", "localhost", "port", 6379, "password", ""),
"test", Map.of("host", "localhost", "port", 6379, "password", ""),
"prod", Map.of("host", "redis-prod.cluster", "port", 6379, "password", "${REDIS_PASSWORD}")
));
// API configurations
context.put("api", Map.of(
"port", getPortForEnvironment(environment),
"corsOrigins", getCorsOrigins(environment),
"rateLimit", getRateLimit(environment)
));
// Feature flags
context.put("features", Map.of(
"newUI", environment.equals("prod") ? false : true,
"experimentalApi", environment.equals("dev") ? true : false,
"maintenanceMode", false
));
// External services
context.put("services", Map.of(
"payment", Map.of(
"url", getServiceUrl("payment", environment),
"timeout", 30000
),
"email", Map.of(
"url", getServiceUrl("email", environment),
"timeout", 10000
)
));
return context;
}
private int getPortForEnvironment(String environment) {
return switch (environment) {
case "dev" -> 8080;
case "test" -> 8081;
case "prod" -> 80;
default -> 8080;
};
}
private String[] getCorsOrigins(String environment) {
return switch (environment) {
case "dev" -> new String[]{"http://localhost:3000", "http://127.0.0.1:3000"};
case "test" -> new String[]{"https://test.example.com"};
case "prod" -> new String[]{"https://app.example.com"};
default -> new String[]{};
};
}
private Map<String, Integer> getRateLimit(String environment) {
return switch (environment) {
case "dev" -> Map.of("requestsPerMinute", 1000, "burstSize", 100);
case "test" -> Map.of("requestsPerMinute", 500, "burstSize", 50);
case "prod" -> Map.of("requestsPerMinute", 100, "burstSize", 20);
default -> Map.of("requestsPerMinute", 100, "burstSize", 10);
};
}
private String getServiceUrl(String service, String environment) {
if ("dev".equals(environment)) {
return "http://localhost:8082/" + service;
} else if ("test".equals(environment)) {
return "https://test-" + service + ".example.com";
} else {
return "https://" + service + ".example.com";
}
}
private void loadEnvironmentConfigs() {
environmentConfigs.put("dev", """
{
"environment": "development",
"debug": true,
"logging": {
"level": "DEBUG",
"file": "logs/app-dev.log"
},
"database": ${database.dev},
"redis": ${redis.dev},
"server": {
"port": ${api.port},
"cors": {
"allowedOrigins": ${api.corsOrigins}
}
},
"rateLimiting": ${api.rateLimit},
"features": ${features},
"externalServices": ${services},
"monitoring": {
"enabled": false,
"endpoint": "/internal/metrics"
}
}
""");
environmentConfigs.put("test", """
{
"environment": "test",
"debug": false,
"logging": {
"level": "INFO",
"file": "logs/app-test.log"
},
"database": ${database.test},
"redis": ${redis.test},
"server": {
"port": ${api.port},
"cors": {
"allowedOrigins": ${api.corsOrigins}
}
},
"rateLimiting": ${api.rateLimit},
"features": ${features},
"externalServices": ${services},
"monitoring": {
"enabled": true,
"endpoint": "/internal/metrics"
}
}
""");
environmentConfigs.put("prod", """
{
"environment": "production",
"debug": false,
"logging": {
"level": "WARN",
"file": "/var/log/app/prod.log",
"jsonFormat": true
},
"database": ${database.prod},
"redis": ${redis.prod},
"server": {
"port": ${api.port},
"cors": {
"allowedOrigins": ${api.corsOrigins}
},
"compression": {
"enabled": true,
"minSize": 1024
}
},
"rateLimiting": ${api.rateLimit},
"features": ${features},
"externalServices": ${services},
"monitoring": {
"enabled": true,
"endpoint": "/internal/metrics",
"prometheus": {
"enabled": true
}
},
"security": {
"ssl": {
"enabled": true,
"certificate": "/etc/ssl/certs/app.crt"
},
"headers": {
"hsts": {
"enabled": true,
"maxAge": 31536000
}
}
}
}
""");
}
public static void main(String[] args) {
ConfigurationTemplateSystem configSystem = new ConfigurationTemplateSystem();
// Generate development configuration
String devConfig = configSystem.generateConfig("dev", Map.of(
"database.dev.poolSize", 8,
"features.newUI", true
));
System.out.println("Development Configuration:");
System.out.println(devConfig);
// Generate production configuration with overrides
Map<String, Object> prodOverrides = new HashMap<>();
prodOverrides.put("database.prod.poolSize", 75);
prodOverrides.put("api.rateLimit.requestsPerMinute", 200);
String prodConfig = configSystem.generateConfig("prod", prodOverrides);
System.out.println("\nProduction Configuration:");
System.out.println(prodConfig);
// Save to file
configSystem.saveConfigToFile("test", null, "config/test-config.json");
}
}
Best Practices
- Template Validation: Validate templates before use
- Security: Sanitize inputs to prevent injection attacks
- Caching: Cache compiled templates for better performance
- Error Handling: Provide meaningful error messages for template issues
- Documentation: Document available placeholders and functions
- Testing: Test templates with various input scenarios
Performance Considerations
- Pre-compilation: Compile templates once, use multiple times
- Caching: Cache rendered results when appropriate
- Lazy Evaluation: Evaluate placeholders only when needed
- Memory Management: Be careful with large templates and recursive structures
JSON template rendering provides a flexible way to generate dynamic JSON content while maintaining separation between data and presentation logic.