🎯 JsonCraft

MIT Licence pub package GitHub stars pub points

Um sistema poderoso e flexível para geração dinâmica de JSON usando templates com interpolação de variáveis, condicionais e formatadores.

✨ Características

  • 🔗 Interpolação de Variáveis: Acesse dados aninhados com {{data.campo}}
  • 🎛️ Condicionais Inteligentes: Inclua/exclua propriedades baseado em condições
  • 🔄 Formatadores Encadeáveis: Transforme dados com sintaxe de pipe
  • 📦 Preservação de Tipos: Mantém tipos originais (arrays, objetos, números)
  • 🚫 Negação: Suporte a condições invertidas com !
  • 🛡️ Tratamento Robusto: Lida graciosamente com valores nulos e inexistentes
  • 🏗️ Arquitetura Extensível: Sistema de formatadores baseado em plugins

🚀 Instalação

import 'lib/json_craft.dart';

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

📖 Guia de Uso

1. 🔗 Interpolação Básica

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

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

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

2. 🎛️ Condicionais

Use {{#if:campo}} para incluir propriedades condicionalmente:

// Template
{
  "nome": "{{data.nome}}",
  "{{#if:data.isVip}}beneficiosVip": ["Frete grátis", "Desconto especial"],
  "{{#if:data.temProdutos}}produtos": "{{data.produtos}}",
  "{{#if:!data.carrinhoVazio}}itensCarrinho": "{{data.carrinho}}"
}

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

// Resultado
{
  "nome": "Ana",
  "beneficiosVip": ["Frete grátis", "Desconto especial"],
  "itensCarrinho": ["item1", "item2"]
}

🔍 Avaliação de Condicionais

Valor {{#if:campo}} {{#if:!campo}}
true ✅ Inclui ❌ Exclui
false ❌ Exclui ✅ Inclui
"" (string vazia) ❌ Exclui ✅ Inclui
[] (array vazio) ❌ Exclui ✅ Inclui
{} (objeto vazio) ❌ Exclui ✅ Inclui
null ❌ Exclui ✅ Inclui
0 ❌ Exclui ✅ Inclui
"texto" ✅ Inclui ❌ Exclui
[1,2,3] ✅ Inclui ❌ Exclui
{"key":"value"} ✅ Inclui ❌ Exclui

3. 🔄 Formatadores

Use a sintaxe de pipe | para aplicar formatadores:

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

// Dados
{
  "data": {
    "nome": "joão silva santos",
    "descricao": "Esta é uma descrição muito longa que precisa ser truncada..."
  }
}

// Resultado
{
  "nomeFormatado": "João Silva Santos",
  "username": "joão_silva_santos", 
  "resumo": "Esta é uma descrição muito longa que precisa ser tr..."
}

📋 Formatadores Disponíveis

🔤 Formatadores de Caso
Formatador Entrada Saída Descrição
pascalCase "joão silva" "JoãoSilva" PascalCase para classes
camelCase "joão silva" "joãoSilva" camelCase para variáveis
snakeCase "João Silva" "joão_silva" snake_case para APIs
kebabCase "João Silva" "joão-silva" kebab-case para URLs
titleCase "joão silva" "João Silva" Title Case para exibição
sentenceCase "JOÃO SILVA" "João silva" Sentence case
upperCase "joão" "JOÃO" MAIÚSCULAS
lowerCase "JOÃO" "joão" minúsculas
replace(name:data.name) "Bem vindo {name}" "Bem vindo João Silva" Substituição de valores
✏️ Formatadores de Texto
Formatador Entrada Saída Descrição
capitalize "joão silva" "João silva" Primeira letra maiúscula
truncate "texto longo..." "texto lon..." Trunca em 100 chars (padrão)
truncate:30 "texto longo..." "texto lon..." Trunca em 30 chars

4. 🔗 Encadeamento de Formatadores

Combine múltiplos formatadores em sequência:

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

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

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

5. 📦 Preservação de Tipos

// Template
{
  "produtosOriginais": "{{data.produtos}}",           // Mantém array
  "produtosFormatados": "{{data.produtos | upperCase}}", // Vira string
  "idadeOriginal": "{{data.idade}}",                  // Mantém número
  "idadeFormatada": "{{data.idade | upperCase}}"      // Vira string
}

6. 🏗️ Exemplo Completo

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 dados = {
    "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 processador = JsonCraft();
  final resultado = processador.process(template, dados);
  
  print(JsonEncoder.withIndent('  ').convert(json.decode(resultado)));
}

Resultado:

{
  "usuario": {
    "nome": "Maria Silva Santos",
    "username": "maria_silva_santos",
    "permissoes": "READ WRITE DELETE"
  },
  "carrinho": {
    "total": 1,
    "primeiroProduto": "Notebook Gamer",
    "resumo": "Notebook para jogos com alta performance e qualid..."
  },
  "configuracoes": {
    "tema": "Dark",
    "idioma": "PT-BR"
  }
}

7. 🔄 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. 📦 Inclusão de Templates

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

🧪 Testes

Execute os testes para verificar todas as funcionalidades:

flutter test

Cobertura atual: 25 testes passando ✅

  • Interpolação básica e aninhada
  • Condicionais e negação
  • Todos os formatadores
  • Encadeamento de formatadores
  • Preservação de tipos
  • Casos edge e tratamento de erros
  • Arquitetura de formatadores extensível

📝 Licença

Este projeto está sob a licença MIT.


Criado com ❤️ usando Flutter e Dart