npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

jsonblade

v1.0.4

Published

A powerful and modular JSON template engine with extensible filters

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 Node Version License: MIT

✨ Why JSONBlade?

  • 🎯 80+ built-in filters - Transform strings, arrays, objects, dates, numbers
  • 🔧 Extensible - Add custom filters and functions
  • 🔀 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 { JSONBlade } from "jsonblade";

const jb = new JSONBlade({ useBuiltins: true });

const template = `{
  "greeting": "Hello {{name}}!",
  "timestamp": "{{now}}",
  "isActive": {{status | equals(active)}}
}`;

const data = {
  name: "World",
  now: new Date().toISOString(),
  status: "active",
};

const result = jb.compile(template, data);

⚡ 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 = new JSONBlade({ useBuiltins: true }).compile(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}}
}`;

🔧 Template 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 { JSONBlade, 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 = new JSONBlade({ useBuiltins: true }).compile(
  template,
  data,
  functions
);
// Result: { apiKey: "your_api_key", sum: 8, phone: "(123) 456-7890", greeting: "Hello World!" }

Asynchronous Functions

import { JSONBlade, 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: "[email protected]" } };
const result = await new JSONBlade({ useBuiltins: true }).compileAsync(
  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 { JSONBlade, TemplateFunction } from "jsonblade";

const asyncFunctions: TemplateFunction[] = [
  {
    name: "fetchUser",
    func: async (id: string) => {
      const response = await fetch(`/api/users/${id}`);
      return response.json();
    },
  },
];

const jb = new JSONBlade({ useBuiltins: true });
const template = `{"user": {{fetchUser(userId)}}}`;
const result = await jb.compileAsync(
  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 a comprehensive set of 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}}
}`;

// Note: engine does not execute async filters in pipeline yet

🔧 Configuration

Customize JSONBlade behavior:

import { setTemplateConfig } from "jsonblade";

setTemplateConfig({
  strictMode: true, // Throw errors instead of warnings
  // no cache settings in current version
  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 = new JSONBlade({ useBuiltins: true }).compile(
  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