jsonblade
Version:
A powerful and modular JSON template engine with extensible filters
702 lines (563 loc) • 18.5 kB
Markdown
# 🗡️ JSONBlade
**A sharp and modular JSON template engine with an extensible filter system.**
_Transform your data into JSON with precision and elegance._
[](https://www.npmjs.com/package/jsonblade)
[](https://www.npmjs.com/package/jsonblade)
[](https://github.com/Synesia/jsonblade)
[](https://www.typescriptlang.org/)
[](https://bundlephobia.com/package/jsonblade)
[](https://www.npmjs.com/package/jsonblade)
[](https://opensource.org/licenses/MIT)
## ✨ Why JSONBlade?
- 🎯 **80+ built-in filters** - Transform strings, arrays, objects, dates, numbers
- 🔧 **Extensible** - Add custom filters and functions
- ⚡ **Fast & cached** - Optimized performance with smart caching
- 🔀 **Advanced features** - Conditions, loops, variables, comments
- 🛡️ **Error resilient** - Graceful handling of missing data
- 📝 **TypeScript native** - Full type support and autocompletion
- 🎨 **Simple syntax** - Easy to learn, powerful to use
- 🔒 **Secure by design** - No hardcoded functions, complete user control
## 📦 Installation
```bash
npm install jsonblade
```
## 🚀 Quick Start
```typescript
import { compileJSONTemplate } from "jsonblade";
const template = `{
"greeting": "Hello {{name}}!",
"timestamp": "{{now}}",
"isActive": {{status | equals('active')}}
}`;
const data = {
name: "World",
now: new Date().toISOString(),
status: "active",
};
const result = compileJSONTemplate(template, data);
// Returns: { greeting: "Hello World!", timestamp: "2023-01-15T10:00:00.000Z", isActive: true }
```
## ⚡ Core Features
### String Interpolation
```typescript
const template = `{
"message": "Welcome {{user.name}}!",
"fullName": "{{user.firstName}} {{user.lastName}}"
}`;
const data = {
user: {
name: "Alice",
firstName: "Alice",
lastName: "Smith",
},
};
const result = compileJSONTemplate(template, data);
```
### Nested Object Access
```typescript
const template = `{
"city": "{{user.address.city}}",
"country": "{{user.address.country}}"
}`;
const data = {
user: {
address: {
city: "Paris",
country: "France",
},
},
};
```
### Array Operations
```typescript
const template = `{
"userCount": {{users | length}},
"firstUser": "{{users | first | get(name)}}",
"userNames": {{users | map(name)}}
}`;
const data = {
users: [
{ name: "Alice", age: 25 },
{ name: "Bob", age: 30 },
],
};
```
## 🔧 Built-in Filters
JSONBlade includes comprehensive filters for data transformation:
### String Filters
- `upper` - Convert to uppercase
- `lower` - Convert to lowercase
- `capitalize` - Capitalize first letter
- `trim` - Remove whitespace
- `default(value)` - Fallback value
### Array Filters
- `length` - Get array/string/object length
- `first` - Get first element
- `last` - Get last element
- `join(separator)` - Join array elements
- `map(property)` - Extract property from objects
- `filter(property, value)` - Filter by property value
### Object Filters
- `keys` - Get object keys
- `values` - Get object values
- `get(property)` - Get property value
- `has(property)` - Check if property exists
- `json` - Serialize to JSON
### Logic Filters
- `equals(value)` - Equality comparison
- `gt(value)` / `gte(value)` - Greater than
- `lt(value)` / `lte(value)` - Less than
- `not` - Boolean negation
- `bool` - Convert to boolean
### Date Filters
- `formatDate(pattern)` - Format date
- `addDays(number)` - Add days
- `isoDate` - ISO format
- `timestamp` - Unix timestamp
### Validation Filters
- `isEmail` - Email validation
- `base64Encode` / `base64Decode` - Base64 operations
- `escape` / `unescape` - HTML escape
- `urlEncode` / `urlDecode` - URL encoding
### Example Usage
```typescript
const template = `{
"formatted": "{{name | upper | trim}}",
"score": {{points | add(bonus) | round(2)}},
"date": "{{created | formatDate('DD/MM/YYYY')}}",
"valid": {{email | isEmail}}
}`;
```
## 🔧 Custom Functions
JSONBlade now supports custom functions that can be called directly in templates using function syntax `{{functionName(args)}}`. **No functions are included by default** - you have complete control over what functions are available.
### Synchronous Functions
```typescript
import { compileJSONTemplate, TemplateFunction } from "jsonblade";
// Define your custom functions
const functions: TemplateFunction[] = [
{
name: "getSecret",
func: (key: string) => process.env[key] || null,
},
{
name: "add",
func: (a: number, b: number) => a + b,
},
{
name: "formatPhone",
func: (phone: string) => {
const cleaned = phone.replace(/\D/g, "");
return cleaned.replace(/(\d{3})(\d{3})(\d{4})/, "($1) $2-$3");
},
},
];
// Use functions in template
const template = `{
"apiKey": "{{getSecret('API_KEY')}}",
"sum": {{add(5, 3)}},
"phone": "{{formatPhone('1234567890')}}",
"greeting": "Hello {{name}}!"
}`;
const data = { name: "World" };
const result = compileJSONTemplate(template, data, functions);
// Result: { apiKey: "your_api_key", sum: 8, phone: "(123) 456-7890", greeting: "Hello World!" }
```
### Asynchronous Functions
```typescript
import { compileJSONTemplateAsync, TemplateFunction } from "jsonblade";
// Define async functions
const asyncFunctions: TemplateFunction[] = [
{
name: "fetchData",
func: async (url: string) => {
const response = await fetch(url);
return response.json();
},
},
{
name: "getCurrentTime",
func: async () => new Date().toISOString(),
},
{
name: "validateEmail",
func: async (email: string) => {
// Simulate async validation
await new Promise((resolve) => setTimeout(resolve, 100));
return email.includes("@") && email.includes(".");
},
},
];
// Use async functions
const asyncTemplate = `{
"data": {{fetchData('https://api.example.com/data')}},
"timestamp": "{{getCurrentTime()}}",
"emailValid": {{validateEmail(user.email)}}
}`;
const data = { user: { email: "test@example.com" } };
const result = await compileJSONTemplateAsync(
asyncTemplate,
data,
asyncFunctions
);
```
### Function vs Filter Syntax
```typescript
// Function syntax: {{functionName(args)}}
"{{getSecret('API_KEY')}}"; // Call function directly
"{{add(5, 3)}}"; // Function with multiple arguments
// Filter syntax: {{value | filterName}}
"{{name | upper}}"; // Transform value through filter
"{{items | length}}"; // Get length of array
```
**When to use functions vs filters:**
- **Functions**: When you need to call operations directly or compute values from scratch
- **Filters**: When you want to transform existing values in a pipeline
### Key Features:
- ✅ **Complete control**: No default functions, define only what you need
- ✅ **Type safety**: Full TypeScript support with `TemplateFunction` interface
- ✅ **Sync and async**: Both synchronous and asynchronous functions supported
- ✅ **Argument parsing**: Supports strings (`'value'`), numbers (`42`), and data paths (`user.name`)
- ✅ **Fallback behavior**: Unknown functions fallback to data path resolution
- ✅ **Secure**: No hardcoded functions that could be security risks
## 🔄 Async Operations
```typescript
import { compileJSONTemplateAsync } from "jsonblade";
// Define async functions for your needs
const asyncFunctions: TemplateFunction[] = [
{
name: "fetchUser",
func: async (id: string) => {
const response = await fetch(`/api/users/${id}`);
return response.json();
},
},
];
const template = `{
"user": {{fetchUser(userId)}},
"timestamp": "{{getCurrentTime()}}"
}`;
const result = await compileJSONTemplateAsync(
template,
{ userId: "123" },
asyncFunctions
);
```
## 🔥 Advanced Templating
JSONBlade supports powerful templating features for complex data transformations:
### Conditions & Loops
```typescript
import { compileAdvancedTemplate } from "jsonblade";
const template = `{
{{#set totalUsers = users | length}}
{{#set activeUsers = users | filter(active, true) | length}}
"summary": {
"total": {{totalUsers}},
"active": {{activeUsers}},
"percentage": "{{activeUsers | divide(totalUsers) | multiply(100) | round(1)}}%"
},
{{#if users | length | gt(0)}}
"userList": [
{{#each users}}
{
"id": {{id}},
"name": "{{name | capitalize}}",
"role": "{{role | upper}}",
"status": "{{#if active}}✅ Active{{#else}}❌ Inactive{{/if}}",
"isLast": {{@last}}
}{{#unless @last}},{{/unless}}
{{/each}}
]
{{#else}}
"userList": []
{{/if}}
}`;
const userData = {
users: [
{ id: 1, name: "alice", role: "admin", active: true },
{ id: 2, name: "bob", role: "user", active: false },
{ id: 3, name: "charlie", role: "moderator", active: true },
],
};
const result = compileAdvancedTemplate(template, userData);
```
### Variables & Comments
```typescript
const template = `{
{{!-- Calculate user statistics --}}
{{#set vipUsers = users | filter(points, 1000, 'gte') | length}}
{{#set averagePoints = users | map(points) | avg | round(0)}}
"analytics": {
"vipCount": {{vipUsers}},
"averagePoints": {{averagePoints}},
"conversionRate": "{{vipUsers | divide(users | length) | percentage}}"
}
}`;
```
## 🛠️ Available Filters
JSONBlade comes with 80+ built-in filters organized by category:
### String Filters
```typescript
"{{name | upper}}"; // "ALICE"
"{{name | lower}}"; // "alice"
"{{name | capitalize}}"; // "Alice"
"{{text | trim}}"; // Remove whitespace
"{{value | default('N/A')}}"; // Fallback value
"{{title | slug}}"; // "my-blog-post"
```
### Array Filters
```typescript
"{{items | join(', ')}}"; // "a, b, c"
"{{items | length}}"; // 3
"{{items | first}}"; // First element
"{{items | last}}"; // Last element
"{{users | map(name)}}"; // Extract property
"{{users | filter(active, true)}}"; // Filter by property
"{{items | reverse}}"; // Reverse array
"{{items | sort}}"; // Sort array
"{{items | unique}}"; // Remove duplicates
```
### Object Filters
```typescript
"{{obj | json}}"; // Serialize to JSON
"{{obj | keys}}"; // Get object keys
"{{obj | values}}"; // Get object values
"{{obj | get(propertyName)}}"; // Get property value
"{{obj | has(propertyName)}}"; // Check if property exists
"{{obj | entries}}"; // Get key-value pairs
```
### Date Filters
```typescript
"{{date | formatDate('DD/MM/YYYY')}}"; // "15/01/2024"
"{{date | fromNow}}"; // "2 hours ago"
"{{date | addDays(7)}}"; // Add days
"{{date | isoDate}}"; // ISO format
```
### Number Filters
```typescript
"{{price | currency('EUR')}}"; // "29,99 €"
"{{ratio | percentage}}"; // "75%"
"{{value | round(2)}}"; // Round decimals
"{{value | multiply(2)}}"; // Math operations
"{{value | add(10)}}"; // Addition
```
### Logic Filters
```typescript
"{{value | equals(42)}}"; // true/false
"{{value | gt(10)}}"; // Greater than
"{{value | bool}}"; // Convert to boolean
"{{text | contains('search')}}"; // Check if contains
"{{value | isEmpty}}"; // Check if empty
```
### Validation Filters
```typescript
"{{email | isEmail}}"; // true/false
"{{url | isUrl}}"; // Validate URL
"{{text | encode}}"; // URL encode
"{{encoded | decode}}"; // URL decode
```
## 🔧 Custom Filters
### Synchronous Filters
Extend JSONBlade with your own filters:
```typescript
import { registerFilter } from "jsonblade";
// Simple filter
registerFilter("exclaim", (text) => `${text}!`);
// Filter with arguments
registerFilter("repeat", (text, times = 2) => text.repeat(times));
// Complex filter
registerFilter("formatPhone", (phone) => {
const cleaned = phone.replace(/\D/g, "");
return cleaned.replace(
/(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/,
"$1 $2 $3 $4 $5"
);
});
// Usage
const template = `{
"message": "{{greeting | exclaim}}",
"echo": "{{word | repeat(3)}}",
"phone": "{{contact | formatPhone}}"
}`;
```
### Asynchronous Filters
Create powerful async filters for external integrations:
```typescript
import { registerAsyncFilter } from "jsonblade";
// Database lookup
registerAsyncFilter("findUser", async (userId) => {
const user = await db.users.findById(userId);
return user || { name: "Unknown", active: false };
});
// API integration
registerAsyncFilter("translateText", async (text, targetLang = "en") => {
const response = await fetch(`https://api.translate.com/v1/translate`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text, target: targetLang }),
});
const result = await response.json();
return result.translatedText;
});
// External validation
registerAsyncFilter("validateEmail", async (email) => {
const response = await fetch(
`https://api.emailvalidation.com/check?email=${email}`
);
const result = await response.json();
return result.valid;
});
// Usage with async compilation
const template = `{
"user": "{{userId | findUser}}",
"message": "{{text | translateText('fr')}}",
"emailValid": {{email | validateEmail}}
}`;
const result = await compileJSONTemplateAsync(template, data);
```
## ⚡ Performance & Caching
JSONBlade automatically caches compiled templates for better performance:
```typescript
import { getCachedTemplate, clearTemplateCache } from "jsonblade";
// Templates are automatically cached
const result1 = compileJSONTemplate(template, data1);
const result2 = compileJSONTemplate(template, data2); // Uses cache
// Manual cache management
const cached = getCachedTemplate("my-template");
clearTemplateCache(); // Clear all cached templates
```
## 🔧 Configuration
Customize JSONBlade behavior:
```typescript
import { setTemplateConfig } from "jsonblade";
setTemplateConfig({
strictMode: true, // Throw errors instead of warnings
maxCacheSize: 1000, // Maximum cached templates
customDelimiters: {
// Custom template delimiters
start: "[[",
end: "]]",
},
});
```
## 🎯 Common Use Cases
### API Response Formatting
```typescript
const apiTemplate = `{
"data": [
{{#each products}}
{
"id": {{id}},
"name": "{{name | capitalize}}",
"price": "{{price | currency('USD')}}",
"inStock": {{stock | gt(0)}},
"rating": {{rating | round(1)}}
}{{#unless @last}},{{/unless}}
{{/each}}
],
"meta": {
"total": {{products | length}},
"generated": "{{timestamp | isoDate}}"
}
}`;
```
### Dynamic Configuration with Custom Functions
```typescript
// Define functions for configuration
const configFunctions: TemplateFunction[] = [
{
name: "getSecret",
func: (key: string) => process.env[key] || null,
},
{
name: "getCurrentTime",
func: () => new Date().toISOString(),
},
];
const configTemplate = `{
"environment": "{{env | upper}}",
"database": {
"host": "{{db.host | default('localhost')}}",
"port": {{db.port | default(5432)}},
"ssl": {{env | equals('production')}},
"password": "{{getSecret('DB_PASSWORD')}}"
},
"services": {
"redis": {
"url": "{{getSecret('REDIS_URL') | default('redis://localhost:6379')}}"
},
"apiKeys": {
"stripe": "{{getSecret('STRIPE_SECRET_KEY')}}",
"sendgrid": "{{getSecret('SENDGRID_API_KEY')}}"
}
},
"metadata": {
"generatedAt": "{{getCurrentTime()}}"
}
}`;
const config = compileJSONTemplate(
configTemplate,
{
env: "production",
db: { host: "prod-db.example.com", port: 5432 },
},
configFunctions
);
```
### Email Templates
```typescript
const emailTemplate = `{
"to": "{{user.email}}",
"subject": "Welcome {{user.name | capitalize}}!",
"body": "Hello {{user.name}}, {{#if user.isPremium}}enjoy your premium features{{#else}}consider upgrading{{/if}}!",
"variables": {
"loginUrl": "{{baseUrl}}/login?token={{user.token | encode}}"
}
}`;
```
### Custom Function Examples
```typescript
// Define application-specific functions
const appFunctions: TemplateFunction[] = [
{
name: "calculateTax",
func: (amount: number, rate: number = 0.1) => amount * rate,
},
{
name: "formatCurrency",
func: (amount: number, currency: string = "USD") => {
return new Intl.NumberFormat("en-US", {
style: "currency",
currency: currency,
}).format(amount);
},
},
{
name: "generateId",
func: () => Math.random().toString(36).substring(2, 15),
},
];
const invoiceTemplate = `{
"id": "{{generateId()}}",
"subtotal": "{{formatCurrency(amount)}}",
"tax": "{{formatCurrency(calculateTax(amount, 0.08))}}",
"total": "{{formatCurrency(add(amount, calculateTax(amount, 0.08)))}}"
}`;
```
## 📚 Error Handling
JSONBlade handles errors gracefully:
```typescript
const template = `{
"safe": "{{missing.property | default('fallback')}}",
"validated": "{{email | isEmail | bool}}",
"calculated": "{{items | length | multiply(price) | currency('EUR')}}"
}`;
// Missing properties return null/empty values
// Unknown filters show warnings but don't break
// Invalid operations are handled gracefully
```
## 📖 Need Help?
For questions, issues, or feature requests, please visit our [GitHub repository](https://github.com/Synesia/jsonblade) or [open an issue](https://github.com/Synesia/jsonblade/issues).
## 📜 License
MIT © [Anthony Jeamme](https://github.com/anthonyjeamme)
---
**Made with ❤️ by [Synesia.ai](https://synesia.ai)**