json_craft 0.2.2 copy "json_craft: ^0.2.2" to clipboard
json_craft: ^0.2.2 copied to clipboard

JsonCraft - Powerful JSON template engine for Flutter/Dart with variable interpolation, smart conditionals, chainable formatters, and type preservation. Generate dynamic JSON with elegant pipe syntax [...]

🎯 JsonCraft #

MIT Licence pub package GitHub stars pub points

A powerful and flexible system for dynamic JSON generation using templates with variable interpolation, conditionals, and formatters.

✨ Features #

  • 🔗 Variable Interpolation: Access nested data with {{data.field}}
  • 🎛️ Smart Conditionals: Include/exclude properties based on conditions
  • 🔄 Chainable Formatters: Transform data with pipe syntax
  • 📦 Type Preservation: Maintains original types (arrays, objects, numbers)
  • 🚫 Negation: Support for inverted conditions with !
  • 🛡️ Robust Handling: Gracefully handles null and non-existent values
  • 🏗️ Extensible Architecture: Plugin-based formatter system
  • 💬 Comments: Document templates with {{! comment }} syntax
  • 🔁 Dot Notation: Implicit iterator for primitive arrays with {{.}}
  • 🔄 Context Change: Simplify templates with {{#with:path}}
  • 🗺️ Map Function: Iterate over arrays to generate dynamic objects
  • 📝 Template Inclusion: Modularize templates with {{#include:id}}
  • 🎯 Dynamic Partials: Data-driven template selection with {{#include:*path}}

🚀 Installation #

import 'lib/json_craft.dart';

final processor = JsonCraft();
final result = processor.process(jsonTemplate, data);

📖 Usage Guide #

1. 🔗 Basic Interpolation #

// Template
{
  "name": "{{data.usuario.nome}}",
  "email": "{{data.usuario.email}}",
  "age": "{{data.usuario.idade}}"
}

// Dados
{
  "data": {
    "usuario": {
      "nome": "João Silva",
      "email": "joao@email.com",
      "idade": 30
    }
  }
}

// Resultado
{
  "name": "João Silva",
  "email": "joao@email.com", 
  "age": 30
}

2. 🎛️ Conditionals #

Use {{#if:field}} to conditionally include properties:

// Template
{
  "name": "{{data.nome}}",
  "{{#if:data.isVip}}vipBenefits": ["Free shipping", "Special discount"],
  "{{#if:data.temProdutos}}products": "{{data.produtos}}",
  "{{#if:!data.carrinhoVazio}}cartItems": "{{data.carrinho}}"
}

// Dados
{
  "data": {
    "nome": "Ana",
    "isVip": true,
    "temProdutos": false,
    "carrinhoVazio": false,
    "carrinho": ["item1", "item2"]
  }
}

// Resultado
{
  "name": "Ana",
  "vipBenefits": ["Free shipping", "Special discount"],
  "cartItems": ["item1", "item2"]
}

🔍 Conditional Evaluation

Value {{#if:field}} {{#if:!field}}
true ✅ Included ❌ Excluded
false ❌ Excluded ✅ Included
"" (empty string) ❌ Excluded ✅ Included
[] (empty array) ❌ Excluded ✅ Included
{} (empty object) ❌ Excluded ✅ Included
null ❌ Excluded ✅ Included
0 ❌ Excluded ✅ Included
"text" ✅ Included ❌ Excluded
[1,2,3] ✅ Included ❌ Excluded
{"key":"value"} ✅ Included ❌ Excluded

3. 🔄 Formatters #

Use the pipe | syntax to apply formatters:

// Template
{
  "formattedName": "{{data.nome | titleCase}}",
  "username": "{{data.nome | lowerCase | snakeCase}}",
  "summary": "{{data.descricao | truncate:50}}"
}

// Dados
{
  "data": {
    "nome": "joão silva santos",
    "descricao": "This is a very long description that needs to be truncated..."
  }
}

// Resultado
{
  "formattedName": "João Silva Santos",
  "username": "joão_silva_santos", 
  "summary": "This is a very long description that needs to be tr..."
}

📋 Available Formatters

🔤 Formatadores de Caso
Formatter Input Output Description
pascalCase "joão silva" "JoãoSilva" PascalCase for classes
camelCase "joão silva" "joãoSilva" camelCase for variables
snakeCase "João Silva" "joão_silva" snake_case for APIs
kebabCase "João Silva" "joão-silva" kebab-case for URLs
titleCase "joão silva" "João Silva" Title Case for display
sentenceCase "JOÃO SILVA" "João silva" Sentence case
upperCase "joão" "JOÃO" UPPERCASE
lowerCase "JOÃO" "joão" lowercase
replace(name:data.name) "Bem vindo {name}" "Bem vindo João Silva" Value substitution
✏️ Text Formatters
Formatter Input Output Description
capitalize "joão silva" "João silva" Capitalizes the first letter
truncate "long text..." "long tex..." Truncates to 100 chars (default)
truncate:30 "long text..." "long tex..." Truncates to 30 chars

4. 🔗 Chaining Formatters #

Combine multiple formatters in sequence:

// Template
{
  "processed": "{{data.texto | lowerCase | titleCase | truncate:20}}"
}

// Dados  
{
  "data": {
    "texto": "ESTE É UM TEXTO MUITO LONGO PARA DEMONSTRAÇÃO"
  }
}

// Resultado
{
  "processed": "Este É Um Texto Muit..."
}

5. 📦 Type Preservation #

// Template
{
  "originalProducts": "{{data.produtos}}",           // Keeps array
  "formattedProducts": "{{data.produtos | upperCase}}", // Becomes string
  "originalAge": "{{data.idade}}",                  // Keeps number
  "formattedAge": "{{data.idade | upperCase}}"      // Becomes string
}

6. 🏗️ Complete Example #

import 'dart:convert';
import 'lib/json_craft.dart';

void main() {
  final template = '''
  {
    "usuario": {
      "nome": "{{data.usuario.nomeCompleto | titleCase}}",
      "username": "{{data.usuario.nomeCompleto | lowerCase | snakeCase}}",
      "{{#if:data.usuario.isAdmin}}permissoes": "{{data.usuario.permissoes | upperCase}}"
    },
    "{{#if:data.produtos}}carrinho": {
      "total": "{{data.produtos.length}}",
      "primeiroProduto": "{{data.produtos.0.nome | titleCase}}",
      "resumo": "{{data.produtos.0.descricao | truncate:50}}"
    },
    "{{#if:!data.carrinhoVazio}}mensagem": "Carrinho vazio",
    "configuracoes": {
      "tema": "{{data.tema | capitalize}}",
      "idioma": "{{data.idioma | upperCase}}"
    }
  }
  ''';

  final data = {
    "data": {
      "usuario": {
        "nomeCompleto": "maria silva santos",
        "isAdmin": true,
        "permissoes": "read write delete"
      },
      "produtos": [
        {
          "nome": "notebook gamer",
          "descricao": "Notebook para jogos com alta performance e qualidade excepcional"
        }
      ],
      "carrinhoVazio": false,
      "tema": "dark",
      "idioma": "pt-br"
    }
  };

  final processor = JsonCraft();
  final result = processor.process(template, data);
  
  print(JsonEncoder.withIndent('  ').convert(json.decode(result)));
}

Result:

{
  "usuario": {
    "nome": "Maria Silva Santos",
    "username": "maria_silva_santos",
    "permissoes": "READ WRITE DELETE"
  },
  "cart": {
    "total": 1,
    "firstProduct": "Notebook Gamer",
    "summary": "Notebook para jogos com alta performance e qualid..."
  },
  "settings": {
    "theme": "Dark",
    "language": "PT-BR"
  }
}

7. 💬 Comments #

Use {{! comment }} to add comments to your templates that will be ignored during processing:

// Template
{
  {{! This is a single-line comment }}
  "name": "{{data.name}}",
  {{!
    This is a multi-line comment
    that can span multiple lines
    and will be completely removed
  }}
  "email": "{{data.email}}"
}

// Data
{
  "data": {
    "name": "John Doe",
    "email": "john@example.com"
  }
}

// Result
{
  "name": "John Doe",
  "email": "john@example.com"
}

📝 Comment Features

  • Single-line comments: {{! This is a comment }}
  • Multi-line comments: Support comments that span multiple lines
  • Documentation: Great for documenting complex templates
  • Clean output: Comments are completely removed before processing

8. 🔁 Dot Notation (Implicit Iterator) #

Use {{.}} to access the current item when iterating over arrays of primitives:

// Template
{
  "{{#map:data.tags}}tagList": {
    "value": "{{.}}",
    "uppercase": "{{. | upperCase}}"
  }
}

// Data
{
  "data": {
    "tags": ["javascript", "dart", "flutter"]
  }
}

// Result
{
  "tagList": [
    {"value": "javascript", "uppercase": "JAVASCRIPT"},
    {"value": "dart", "uppercase": "DART"},
    {"value": "flutter", "uppercase": "FLUTTER"}
  ]
}

🔍 Dot Notation Features

  • Primitive arrays: Works with arrays of strings, numbers, or booleans
  • Formatters: Apply formatters to primitive values: {{. | upperCase}}
  • Backward compatible: Existing {{item.property}} syntax still works for objects
  • Type preservation: When used as complete placeholder, preserves number types

9. 🔄 Context Change (With) #

Use {{#with:path}} to change the context and avoid repeating long paths:

// Template WITHOUT context change (repetitive)
{
  "userName": "{{data.user.name}}",
  "userEmail": "{{data.user.email}}",
  "userAge": "{{data.user.age}}",
  "userCity": "{{data.user.address.city}}"
}

// Template WITH context change (clean)
{
  "{{#with:data.user}}profile": {
    "userName": "{{name}}",
    "userEmail": "{{email}}",
    "userAge": "{{age}}",
    "userCity": "{{address.city}}"
  }
}

// Data
{
  "data": {
    "user": {
      "name": "John Doe",
      "email": "john@example.com",
      "age": 30,
      "address": {
        "city": "São Paulo"
      }
    }
  }
}

// Result
{
  "profile": {
    "userName": "John Doe",
    "userEmail": "john@example.com",
    "userAge": 30,
    "userCity": "São Paulo"
  }
}

🎯 Context Change Features

  • Cleaner templates: Avoid repeating long paths
  • Nested contexts: Support for {{#with}} inside another {{#with}}
  • Parent context access: Fields not found in new context fall back to parent
  • Works with formatters: Apply formatters within the new context
  • Combines with other functions: Use with {{#if}}, {{#map}}, etc.

Example: Nested Context

{
  "{{#with:data.company}}companyInfo": {
    "name": "{{name}}",
    "{{#with:employees.manager}}manager": {
      "name": "{{name}}",
      "{{#with:contact}}contact": {
        "email": "{{email}}",
        "phone": "{{phone}}"
      }
    }
  }
}

10. 🔄 Map #

Use {{#map:campo}} para iterar sobre arrays e gerar objetos dinâmicos:

// Template
{
  "{{#map:data.usuarios}}usuarios": {
    "titulo": "{{translate.bemVindo}} {{item.nome}} - {{item.idade}}"
  }
}

// Dados
{
  "translate": {"bemVindo": "Bem vindo"},
  "data": {
    "usuarios": [
      {"nome": "Rafael", "idade": 32},
      {"nome": "Ana", "idade": 35}
    ]
  }
}

// Resultado
{
  "usuarios": [
    {"titulo": "Bem vindo Rafael - 32"},
    {"titulo": "Bem vindo Ana - 35"}
  ]
}

🔍 Avaliação de Map

Valor {{#map:campo}}
[] (array vazio) ❌ Exclui
[1,2,3] ✅ Itera

O map permite criar objetos dinâmicos baseados em arrays, com suporte a interpolação e formatadores.

8. 🎯 Dynamic Partials #

Dynamic Partials allow you to choose which template to include based on data, making your templates truly data-driven!

Static Include (nome fixo)

{
  "content": "{{#include:userTemplate}}"  // Always uses "userTemplate"
}

Dynamic Include (nome vem dos dados)

// Template
{
  "card": "{{#include:*data.cardType}}"  // * indicates dynamic
}

// Data - Scenario 1
{
  "data": {
    "cardType": "userTemplate",
    "name": "John"
  }
}

// Data - Scenario 2
{
  "data": {
    "cardType": "adminTemplate",
    "name": "Alice"
  }
}

🔥 Real-World Use Cases

1. Multi-tenancy / White Label

// Single template for all clients
{
  "branding": "{{#include:*client.themeTemplate}}"
}

// Each client can have different template
// Client A: themeTemplate = "clientA_theme"
// Client B: themeTemplate = "clientB_theme"

2. Dynamic Components

{
  "{{#map:data.widgets}}widgets": {
    "widget": "{{#include:*item.type}}"
  }
}

// Each widget uses its own template based on type
// buttonWidget, textWidget, imageWidget, etc.

3. Dynamic Forms

{
  "{{#map:data.fields}}formFields": {
    "field": "{{#include:*item.fieldType}}"
  }
}

// Different field types: inputField, selectField, checkboxField

🎯 Benefits

  • Data-driven: Template selection based on data
  • Zero conditionals: No need for multiple {{#if}} statements
  • Scalable: Add new templates without changing main template
  • Flexible: Works with {{#map}}, {{#with}}, and all other features

9. 📦 Template Inclusion (Static) #

Agora é possível incluir templates adicionais no processamento usando o placeholder especial {{#include:id}}. Isso permite modularizar e reutilizar partes do JSON.

Exemplo de Uso

// Template principal
{
  "titulo": "{{titulo}}",
  "conteudo": "{{#include:subTemplate}}"
}

// Template adicional
{
  "subtitulo": "{{subtitulo}}",
  "detalhes": "{{detalhes}}"
}

// Dados
{
  "titulo": "Título Principal",
  "subtitulo": "Subtítulo",
  "detalhes": "Alguns detalhes aqui."
}

// Resultado
{
  "titulo": "Título Principal",
  "conteudo": {
    "subtitulo": "Subtítulo",
    "detalhes": "Alguns detalhes aqui."
  }
}

Como Usar

Passe os templates adicionais como um mapa no método process:

final mainTemplate = json.encode({
  'titulo': '{{titulo}}',
  'conteudo': '{{#include:subTemplate}}'
});

final subTemplate = json.encode({
  'subtitulo': '{{subtitulo}}',
  'detalhes': '{{detalhes}}'
});

final templates = {
  'subTemplate': subTemplate
};

final data = {
  'titulo': 'Título Principal',
  'subtitulo': 'Subtítulo',
  'detalhes': 'Alguns detalhes aqui.'
};

final resultado = JsonCraft().process(mainTemplate, data, templates: templates);
print(resultado);

Tratamento de Erros

  • Template ausente: Lança exceção se o template referenciado não for encontrado.
  • Placeholder inválido: Lança exceção para sintaxe incorreta.

🎯 Casos de Uso #

🏷️ Geração de Identificadores #

{
  "className": "{{data.nome | pascalCase}}",
  "variableName": "{{data.nome | camelCase}}",
  "apiEndpoint": "/{{data.nome | kebabCase}}",
  "dbField": "{{data.nome | snakeCase}}"
}

📄 Formatação de Conteúdo #

{
  "titulo": "{{data.artigo.titulo | titleCase}}",
  "resumo": "{{data.artigo.conteudo | truncate:150}}",
  "autor": "{{data.artigo.autor | titleCase}}",
  "tags": "{{data.artigo.tags | upperCase}}"
}

🔐 Configurações Condicionais #

{
  "{{#if:data.usuario.isPremium}}features": ["feature1", "feature2"],
  "{{#if:!data.usuario.isGuest}}profile": {
    "name": "{{data.usuario.nome | titleCase}}",
    "settings": "{{data.configuracoes}}"
  }
}

🏗️ Arquitetura Extensível #

Formatadores Customizados #

Você pode criar seus próprios formatadores:

import 'lib/json_craft.dart';
import 'lib/json_craft_formatter.dart';

// Criar formatador customizado
final customFormatter = JsonCraftFormatter(
  name: 'reverse',
  formatter: (value, param) => value.split('').reversed.join(),
);

// Usar com formatadores customizados
final processor = JsonCraft(formatters: [customFormatter]);
final resultado = processor.process('{"reversed": "{{data.text | reverse}}"}', data);

Sistema de Plugins #

A arquitetura baseada em JsonCraftFormatter permite:

  • Formatadores customizados
  • Extensibilidade fácil
  • Reutilização de código
  • Testes isolados
  • Manutenibilidade

🛡️ Tratamento de Erros #

O sistema trata graciosamente:

  • Campos inexistentes: Lança exceção com detalhes
  • Índices inválidos: Lança exceção para arrays
  • Formatadores inexistentes: Retorna valor original
  • Valores nulos: Retorna string vazia
  • Condicionais inválidas: Retorna false

🧪 Tests #

Run tests to verify all functionalities:

flutter test

Current coverage: 55 tests passing ✅

  • Basic and nested interpolation
  • Conditionals and negation
  • All formatters
  • Formatter chaining
  • Type preservation
  • Edge cases and error handling
  • Extensible formatter architecture
  • Dot notation with primitive arrays
  • Comments (single-line and multi-line)
  • Context change with nested contexts
  • Map function with arrays
  • Template inclusion (static and dynamic)

📝 Licença #

Este projeto está sob a licença MIT.


Criado com ❤️ usando Flutter e Dart

1
likes
130
points
1.58k
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

JsonCraft - Powerful JSON template engine for Flutter/Dart with variable interpolation, smart conditionals, chainable formatters, and type preservation. Generate dynamic JSON with elegant pipe syntax and extensible formatter architecture.

Homepage
Repository (GitHub)

License

MIT (license)

More

Packages that depend on json_craft