Menu

JSON no SQLite: json_extract, json_set e json_each

Como o SQLite armazena e consulta JSON: extrair campos, atualizar valores, percorrer arrays com json_each e indexar caminhos JSON para ganhar performance.

Esta página tem editores executáveis — edite, execute e veja a saída na hora.

O SQLite não tem tipo JSON — e tudo bem

O SQLite não tem uma coluna dedicada para JSON. O JSON entra numa coluna TEXT comum, e um conjunto de funções nativas — conhecido como extensão JSON1 — sabe interpretar, consultar e alterar esse conteúdo. A JSON1 já vem em qualquer build moderno do SQLite, então não precisa instalar nada.

O modelo mental é simples: você guarda o documento como texto e usa funções para olhar dentro dele.

Duas linhas, cada uma guardando um documento JSON numa coluna de texto comum. Agora precisamos de formas de chegar até esses documentos.

Extraindo campos com json_extract e ->>

A função json_extract(column, path) extrai um valor de dentro de um documento JSON. O caminho começa com $ (a raiz) e usa .campo para chaves de objeto e [i] para índices de array.

Escrever json_extract(data, '$.name') em todo lugar cansa rápido, e por isso o SQLite oferece dois operadores:

  • -> devolve um valor codificado em JSON (strings vêm com aspas).
  • ->> devolve um valor SQL (texto ou número, sem aspas).

name_json retorna "Ada" (ainda em JSON), enquanto name_text devolve Ada. Use ->> quando quiser comparar ou exibir o valor. Já o -> é a escolha certa quando o resultado vai alimentar outra função JSON.

Filtrando por campos JSON

Depois que você domina a extração, filtrar é o passo natural. A expressão entra na cláusula WHERE como qualquer outra:

Funciona, mas em uma tabela de tamanho considerável fica lento — cada linha precisa ser parseada para avaliar o predicado. Daqui a pouco resolvemos isso com um índice.

Construindo JSON: json_object e json_array

No caminho inverso, dá para montar JSON dentro de uma consulta:

json_object('k1', v1, 'k2', v2, ...) monta um objeto. Já json_array(v1, v2, ...) monta um array. São funções bem práticas para montar respostas de API direto no SQL, e dá para aninhar uma dentro da outra sem dor de cabeça:

Atualizando JSON: json_set, json_insert e json_replace

O SQLite oferece três funções bem parecidas para modificar um documento JSON e devolver a versão atualizada:

  • json_set(doc, path, value) — define o valor no caminho informado, criando se não existir e sobrescrevendo se já existir.
  • json_insert(doc, path, value) — só insere quando o caminho ainda não existe.
  • json_replace(doc, path, value) — só atualiza quando o caminho já existe.

Vale lembrar: nenhuma dessas funções altera o documento no lugar. Elas retornam um novo JSON, que você normalmente grava de volta na tabela com um UPDATE:

Vale lembrar que o json_set aceita vários pares de caminho/valor numa mesma chamada. Para remover uma chave, use json_remove(doc, path).

Expandindo arrays com json_each

O json_each no SQLite é uma função que retorna uma tabela: você passa um array JSON (ou um objeto) e ela devolve uma linha por elemento. Com isso, algo como "encontrar os usuários que têm a tag admin" — que seria meio chato em SQL puro — vira um JOIN comum:

Cada linha de users é cruzada com os elementos do seu array tags. O json_each expõe colunas bem úteis, como key, value, type e fullkey. Já o irmão json_tree percorre o documento inteiro de forma recursiva, passando por cada nó aninhado — ótimo quando você precisa varrer documentos de formato desconhecido.

Criando índice em campo JSON no SQLite

A consulta WHERE data ->> '$.active' = 1 mostrada acima funciona, mas o SQLite precisa fazer o parse de cada linha para avaliar o predicado. Para campos que você consulta com frequência, vale criar um índice de expressão:

O índice precisa usar exatamente a mesma expressão da sua consulta. Misturar json_extract(data, '$.email') no índice com data ->> '$.email' na consulta não casa, e o índice fica encostado sem ser usado — escolha uma forma e mantenha ela.

Para campos que você consulta o tempo todo, uma coluna gerada deixa o código mais limpo:

email se comporta como uma coluna comum aos olhos de quem escreve as queries, mas continua sincronizado com o JSON automaticamente.

Validando JSON no SQLite

A função json_valid(text) devolve 1 quando o texto é um JSON válido e 0 caso contrário. Combine com uma constraint CHECK para barrar dados inválidos já na hora da escrita:

O primeiro INSERT passa numa boa; o segundo já estoura com erro de constraint. Sem essa checagem, um JSON malformado fica ali, quietinho na tabela, até que algum json_extract exploda meses depois.

JSON vs JSONB no SQLite

A partir do SQLite 3.45, existe uma representação binária chamada JSONB — os mesmos dados, só que já pré-processados num formato binário compacto, evitando que as funções precisem fazer o parse a cada chamada. A família de funções jsonb_* (jsonb_extract, jsonb_set, jsonb_object, ...) devolve JSONB em vez de texto, e colunas JSONB podem ser consultadas com os mesmos operadores.

Use JSON puro (texto) quando quiser que os documentos sejam legíveis em dumps e fáceis de inspecionar. Vá de JSONB quando a tabela for grande, consultada com frequência e o custo de parsing realmente aparecer no profiling. Não troque por padrão — a legibilidade do JSON puro vale muito na hora de debugar.

Quando JSON é a escolha certa

Colunas JSON brilham nestes casos:

  • O formato varia de linha para linha (pense em payloads de eventos, logs de auditoria, webhooks de integração).
  • Você está cacheando a resposta de uma API externa e quer mantê-la intacta.
  • Um campo raramente é consultado e quase nunca aparece em filtros.

São uma má escolha quando:

  • Você usa JSON pra fugir de modelar um schema. Se toda linha tem os mesmos campos, isso são colunas.
  • Você precisa filtrar ou fazer join num valor com frequência. Uma coluna de verdade com índice sempre vai ser mais rápida que uma busca por JSON path.
  • Você usaria foreign keys. JSON não tem integridade relacional.

O ponto ideal é misturar os dois: colunas escalares para os campos que guiam consultas e restrições, e uma coluna JSON do lado para a cauda longa dos dados variáveis.

A seguir: Busca Full-Text

JSON te dá flexibilidade no armazenamento. A próxima página cobre o FTS5 — o motor de busca full-text do SQLite — que oferece busca textual de verdade, com ranqueamento e destaque, muito além do que o LIKE consegue entregar.

Perguntas frequentes

Como o SQLite armazena JSON?

O SQLite não tem um tipo dedicado para JSON — tudo é guardado como TEXT puro. Quem faz o trabalho pesado é a extensão JSON1 (compilada por padrão desde a versão 3.38), que oferece funções como json_extract, json_set e json_each para fazer parse e manipular esse texto. A partir da 3.45 também existe o formato binário JSONB, que acelera acessos repetidos.

Como consultar uma coluna JSON no SQLite?

Use json_extract(coluna, '$.caminho') ou o atalho com o operador ->>. Por exemplo, SELECT data ->> '$.name' FROM users retorna o campo name de um documento JSON guardado em data. Nos paths, $ representa a raiz, .campo acessa chaves de objeto e [i] acessa índices de array.

Dá para indexar um campo JSON no SQLite?

Dá sim — basta criar um índice de expressão sobre o caminho extraído: CREATE INDEX idx_user_email ON users(json_extract(data, '$.email')). Consultas que usarem a mesma expressão no WHERE aproveitam esse índice. Quando o campo é consultado com frequência, geralmente fica mais limpo criar uma coluna gerada (generated column) e indexá-la.

Qual a diferença entre -> e ->> no SQLite?

O -> devolve um valor JSON (ainda codificado como JSON — strings vêm com aspas), enquanto o ->> devolve um valor SQL puro (texto ou número, sem aspas). Use ->> quando quiser o valor cru para exibir ou comparar; use -> quando for encadear outras operações JSON em cima do resultado.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR