JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 6
  • Score
    100M100P100Q41587F
  • License MIT

A powerful and modular JSON template engine with extensible filters

Package Exports

  • jsonblade
  • jsonblade/package.json

Readme

🗡️ JSONBlade

A sharp and modular JSON template engine with an extensible filter system.

Transform your data into JSON with precision and elegance.

npm version npm downloads Coverage TypeScript Bundle Size Node Version License: 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

npm install jsonblade

🚀 Quick Start

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

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

const template = `{
  "city": "{{user.address.city}}",
  "country": "{{user.address.country}}"
}`;

const data = {
  user: {
    address: {
      city: "Paris",
      country: "France",
    },
  },
};

Array Operations

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

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

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

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

// 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

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

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

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

"{{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

"{{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

"{{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

"{{date | formatDate('DD/MM/YYYY')}}"; // "15/01/2024"
"{{date | fromNow}}"; // "2 hours ago"
"{{date | addDays(7)}}"; // Add days
"{{date | isoDate}}"; // ISO format

Number Filters

"{{price | currency('EUR')}}"; // "29,99 €"
"{{ratio | percentage}}"; // "75%"
"{{value | round(2)}}"; // Round decimals
"{{value | multiply(2)}}"; // Math operations
"{{value | add(10)}}"; // Addition

Logic Filters

"{{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

"{{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:

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:

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:

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:

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

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

// 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

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

// 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:

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 or open an issue.

📜 License

MIT © Anthony Jeamme


Made with ❤️ by Synesia.ai