Vínculo de dados e templates
Cada campo aponta pro JSON real via um Binding:
scalar— um valor direto (caminho.no.json).template— texto livre com{token}, ex.:"Cliente: {nome} — Total: {SUM(rows.total)}".array— uma tabela vinda de um array de objetos, coluna por coluna, incluindo coluna calculada (rótulo fixo + fórmula avaliada por linha — pode combinar texto fixo com mais de um token:"{pnr} - {produto}").keyvalue— tabela "campo / valor" a partir de uma lista de paths escolhidos manualmente.section— o array que uma seção repete, um item por repetição.chart— um array a agregar num gráfico pizza/barra (path,labelColumn,valueColumn,filtersopcional) — veja Gráficos.kpi— um array a agregar (sum/count/avg/min/max) num número só pra um cartão KPI, sobrescrevendo ovaluelivre — veja Cartões KPI.
Escrevendo um caminho
Um caminho pode ser escrito nu ou delimitado, e a diferença importa por um
motivo só: o caminho nu quebra no ., então uma chave de JSON cujo nome
contém ponto não tem forma nua.
| Escrito | Resolve |
|---|---|
{id} | a chave id |
{cliente.nome} | caminha cliente → nome |
{[id]} | a chave id — a forma delimitada da mesma coisa |
{[cliente].[nome]} | caminha cliente → nome |
{[cliente.nome]} | a chave literal "cliente.nome"; ponto dentro do bracket não separa |
{["token name"]} | a chave "token name" |
{[total] + 1} | conta — o operador está fora do bracket |
{CURRENCY([total], "R$", 2)} | caminho delimitado como argumento de função |
Duas regras que vale dizer na cara:
- Segmento com espaço tem que vir entre aspas —
["minha chave"], não[minha chave]. Neste formato um operador só é operador com espaço dos dois lados, então[a + b]seria ambíguo entre a chave"a + b"e uma conta dentro do bracket. É recusado em vez de adivinhado. - As aspas são
"ou', a que o conteúdo permitir. Não há caractere de escape; chave que contém"se escreve entre'. OtokenForescolhe por você.
A forma nua não está depreciada e não vai ficar — {cliente.nome}, {my-key}
e {my key} são os caminhos que sempre foram. Use a delimitada quando a chave
precisar, ou sempre, por uniformidade: o editor escreve ela em todo lugar e
mostra a forma curta na célula do canvas, então {[id]} é o que fica gravado e
{id} é o que você lê.
{[a]} era a chave literalmente chamada [a], porque o lexer absorvia os
brackets no identificador. Agora é o caminho a. Se você tem uma chave de JSON
com bracket no nome, escreva {["[a]"]}.
Funções de template
Dentro de {...} (texto, coluna calculada, célula de rodapé — em
qualquer lugar):
| Função | O que faz |
|---|---|
SUM, COUNT, AVG | Agrega um path de array. |
CONCAT, UPPER, LOWER | Helpers de string. |
TRIM(caminho) | Tira espaço do início/fim — útil pra campo de sistema legado com largura fixa ("fatura": " 01156189"). {token}/CONCAT preservam o valor exatamente como veio, de propósito — o espaço só some se você pedir. |
DATE(caminho, "saída"[, "entrada"]) | Formata uma data — ver abaixo. |
CURRENCY(caminho, "R$") | Formata um número com símbolo de moeda. |
NUMBER(caminho, casas) | Tipo %.2f do C — só casas decimais, sem símbolo/separador de milhar (isso é o CURRENCY). |
IF(condição, "então", "senão") | Condicional — ver abaixo. |
Aritmética simples também funciona: {qtd * preco},
{subtotal - desconto}.
Como uma expressão é lida. * e / ligam mais forte que + e -, e
parênteses agrupam:
{qtd * preco + taxa} -> qtd * preco, depois + taxa
{(base + taxa) * qtd} -> base + taxa primeiro, depois * qtd
Uma regra é incomum e vale conhecer, porque é ela que permite uma chave JSON com hífen ou com espaço: um operador só é operador quando tem espaço em branco dos dois lados.
{my-key} -> o path "my-key" (não "my menos key")
{my key} -> o path "my key"
{a - b} -> subtração
{a -b} -> nenhum dos dois: o path "a -b", que normalmente dá vazio
O mesmo vale pros operadores de comparação dentro do IF: IF(a == 2, …) é
comparação, IF(a==2, …) é um path chamado a==2.
Expressão que não consegue produzir número resolve pra vazio em vez de
falhar: {"x" + 1}, {a / 0} e um path que não existe se comportam assim (um
path ausente conta como 0 dentro de uma soma, então {naoexiste + a} dá o
valor de a). Erro de sintaxe — aspas não fechadas, parêntese
aberto — também resolve aquele campo pra vazio em vez de derrubar o documento
inteiro: uma vírgula esquecida não pode custar um relatório de 200 páginas. A
posição do erro é reportada no editor (o aviso do campo, e ao vivo dentro do
editor ƒx), que é onde ainda dá pra consertar. O
expressionError(source, t?) dá a mesma mensagem programaticamente, e o
parse estoura se você quiser a versão estrita.
Uma função pode receber outra função ou uma expressão aritmética como
argumento ({CURRENCY(SUM(rows.total), "R$")}), incluindo duas
combinadas por operador ({SUM(a) - SUM(b)} subtrai certo), com uma
exceção: o argumento do próprio SUM, COUNT e AVG é sempre lido
como um path de array cru, nunca resolvido como outra chamada de
função aninhada — {SUM(CONCAT(a, b))} não funciona, tem que ser um
path simples tipo rows.total. O resultado deles continua podendo
entrar dentro de outra função por fora sem problema (é isso que faz
{CURRENCY(SUM(rows.total), "R$")} acima funcionar — quem resolve o
próprio argumento ali é o CURRENCY, não o SUM).
IF(condição, "então", "senão") escolhe um dos dois últimos
argumentos — condição é uma comparação (status == "paid",
total > 100; operadores ==, !=, >, >=, <, <=, sempre
cercados de espaço) ou um path/expressão isolada, checado como
verdadeiro/falso (string vazia, "0" e "false" contam como falso,
qualquer outra coisa como verdadeiro). Só o lado escolhido é resolvido
de verdade — {IF(temDesconto, valorDesconto, "0")} não quebra mesmo
que valorDesconto não exista no dado quando temDesconto é falso.
Datas são sempre lidas/escritas em UTC
O 3º argumento (opcional) do DATE diz o formato de entrada — sem
ele, new Date(raw) do JS tenta adivinhar, e uma data tipo
"10/04/2025" (dia 10) vira 10 de outubro (formato americano).
Informando DATE(vencto, "DD/MM/YYYY", "DD/MM/YYYY"), lê exatamente
como escrito, sem ambiguidade.
As datas são sempre lidas/escritas em UTC — não no fuso do
navegador/servidor que gera o PDF. Uma data só ("2026-07-01", sem
hora) sai igual ao que foi escrito, não importa onde rodar; um datetime
com fuso explícito ("...T23:30:00-03:00") é convertido pro instante
UTC equivalente.
Escrever uma expressão: o editor ƒx
O <Designer> tem um botão ƒx ao lado de todo campo que aceita expressão:
fórmula de coluna de tabela, cada célula da linha de totais,
título/valor/legenda do KPI e conteúdo de um campo de texto. Ele abre uma
janela com
- os campos a que o schema está vinculado, à esquerda, em dois grupos.
Campos de cada item do array vinculado (
total, sem prefixo) são os que resolvem dentro de uma linha de tabela ou de uma seção repetida; os caminhos completos (faturas.total) são o que uma agregação precisa. UmSUM(total)com o campo do primeiro grupo não acharia nada — os dois escopos são a razão de a lista ser dividida. - um editor multilinha com autocomplete, oferecendo as funções acima mais
AND/OR/NOT, com a dica de cada uma. Ele escreve os espaços que um operador precisa nos dois lados, então não consegue produzir operador de um lado só. As sugestões aparecem só dentro das chaves — fora delas é texto literal, e uma lista de funções ali só estorva. - validação ao vivo — as mesmas mensagens do aviso de campo do editor. Erro de sintaxe bloqueia o salvar; operador suspeito só avisa.
O editor guarda o valor do campo em si, com as chaves, aberto já com o que
estava lá. Não há um segundo campo pra compor: a edição é no lugar, e prefixo
literal fica onde está (FAT-{fatura}). Clicar num campo da esquerda insere o
caminho nu dentro das chaves, ou {caminho} fora delas.
Ele também acusa chave desbalanceada, coisa que nada mais faz: o
resolvedor de template casa /\{([^{}]+)\}/g, então uma { sem par
simplesmente não casa e aquele trecho sai como texto literal no PDF —
{CURRENCY(total impresso na cara. Não é erro de sintaxe de expressão (o
parser nunca vê esse trecho) nem falha de geração; era só um campo saindo
errado em silêncio.
As peças são exportadas pra quem monta UI própria: suggestAt,
applySuggestion, insertAtCaret, wordAtCaret, ALL_SUGGESTIONS, mais
tokenAtCaret e braceError pro lado das chaves (também no
json-pdf-designer/server — são puras).
Funções públicas pra um vínculo de UI próprio
buildInputs(data, bindings) // resolve todos os vínculos de uma vez
renderTemplate(template, data) // resolve um template livre "{token}"
resolveToken(token, data) // resolve um token/função isolado
rowsFromArrayBinding(list, columns) // array de objetos -> linhas de tabela
columnLabel(col), columnKey(col)
describeBinding(b, t?), describeBindingShort(b, t?) // t: Dict — default inglês
CUSTOM_FIELD_FUNCTIONS // lista das funções disponíveis