Se você já ficou olhando para uma resposta de API profundamente aninhada pensando "eu só preciso daquele campo enterrado lá dentro" — JSONPath é a ferramenta que você está procurando. É uma linguagem de consulta para JSON, semelhante em espírito ao que o XPath faz para XML. Você escreve uma expressão de caminho e obtém de volta os valores correspondentes. Sem loops, sem travessia manual. Stefan Goessner introduziu isso em 2007, e após anos de implementações ligeiramente incompatíveis circulando por aí, foi formalmente padronizado como RFC 9535 em 2024. Você também pode explorar expressões JSONPath interativamente com a ferramenta JSON Path.

O Dataset Que Usaremos ao Longo do Artigo

Em vez de exemplos abstratos, vamos trabalhar com algo realista — uma resposta de pedido de e-commerce. É o tipo de JSON que você receberia de uma API de gerenciamento de pedidos:

json
{
  "order": {
    "id": "ORD-9182",
    "status": "shipped",
    "customer": {
      "id": "CUST-441",
      "name": "Maria Chen",
      "email": "[email protected]"
    },
    "shippingAddress": {
      "street": "14 Maple Avenue",
      "city": "Portland",
      "state": "OR",
      "zip": "97201",
      "country": "US"
    },
    "lineItems": [
      {
        "sku": "WH-1042",
        "name": "Wireless Headphones",
        "qty": 1,
        "unitPrice": 89.99,
        "inStock": true
      },
      {
        "sku": "CB-USB-C",
        "name": "USB-C Charging Cable",
        "qty": 2,
        "unitPrice": 12.49,
        "inStock": true
      },
      {
        "sku": "SC-PRO-7",
        "name": "Phone Stand Pro",
        "qty": 1,
        "unitPrice": 34.00,
        "inStock": false
      }
    ],
    "totals": {
      "subtotal": 148.97,
      "shipping": 5.99,
      "tax": 11.92,
      "total": 166.88
    }
  }
}

O Operador Raiz $ e Navegação Básica

Toda expressão JSONPath começa com $, que se refere à raiz do documento. A partir daí, você navega usando pontos. Quer o status do pedido? É simples:

text
$.order.status
// → "shipped"

$.order.customer.name
// → "Maria Chen"

$.order.totals.total
// → 166.88

$.order.shippingAddress.city
// → "Portland"

Você também pode usar a notação de colchetes, útil quando uma chave contém espaços ou caracteres especiais, ou quando você está acessando elementos de array por índice. Ambos os estilos são válidos de acordo com as regras de sintaxe do RFC 9535:

text
// Notação de ponto
$.order.customer.email

// Notação de colchetes — equivalente
$['order']['customer']['email']

// Índice de array — primeiro item
$.order.lineItems[0].name
// → "Wireless Headphones"

// Último item (indexação negativa — suportada no RFC 9535)
$.order.lineItems[-1].name
// → "Phone Stand Pro"
Atenção à indexação negativa: $[-1:] (sintaxe de slice) e $.array[-1] (índice negativo direto) são ambos válidos no RFC 9535, mas algumas implementações mais antigas não os suportam. Teste na sua biblioteca específica se estiver usando índices negativos.

Wildcards — Consultar Todos os Filhos de Uma Vez

O wildcard * corresponde a todos os elementos em um determinado nível. Em vez de pedir lineItems[0], lineItems[1], etc., você pode pegar tudo de uma vez:

text
// Todos os nomes dos itens
$.order.lineItems[*].name
// → ["Wireless Headphones", "USB-C Charging Cable", "Phone Stand Pro"]

// Todos os preços unitários
$.order.lineItems[*].unitPrice
// → [89.99, 12.49, 34.00]

// Todos os SKUs
$.order.lineItems[*].sku
// → ["WH-1042", "CB-USB-C", "SC-PRO-7"]

Este é o padrão que você usará com mais frequência ao trabalhar com arrays em respostas de API. Em vez de fazer um map sobre um array no seu código de aplicação, você pode extrair exatamente o campo que precisa antes que ele chegue à sua lógica de negócios.

Descida Recursiva — Encontrando Valores em Qualquer Profundidade

O operador .. faz uma busca recursiva por toda a árvore do documento. É como uma varredura em profundidade que coleta cada nó que corresponde ao nome da chave, independentemente de onde ele esteja:

text
// Encontrar todo campo "name" em qualquer lugar do documento
$..name
// → ["Maria Chen", "Wireless Headphones", "USB-C Charging Cable", "Phone Stand Pro"]

// Encontrar todo campo "city" em qualquer lugar
$..city
// → ["Portland"]

// Encontrar todo campo "id" em qualquer profundidade
$..id
// → ["ORD-9182", "CUST-441"]

Observe como $..name encontra tanto o nome do cliente quanto todos os nomes de produtos — não se importa com a profundidade. Isso é poderoso para exploração de schema: quando você recebe um blob JSON desconhecido e quer encontrar todos os valores de uma chave específica sem conhecer a estrutura. Se quiser verificar o próprio JSON primeiro, o JSON Validator e o JSON Formatter são ótimos pontos de partida.

Fatiamento de Arrays

JSONPath toma emprestada a notação de slice no estilo Python para arrays. O formato é [start:end:step], onde qualquer parte pode ser omitida. Isso está documentado em RFC 9535 § 2.3.5:

text
// Primeiros dois itens
$.order.lineItems[0:2]
// → [{ sku: "WH-1042", ... }, { sku: "CB-USB-C", ... }]

// Do índice 1 até o final
$.order.lineItems[1:]
// → [{ sku: "CB-USB-C", ... }, { sku: "SC-PRO-7", ... }]

// Apenas o último item (sintaxe de slice — amplamente suportada)
$.order.lineItems[-1:]
// → [{ sku: "SC-PRO-7", ... }]

// Um item sim, um não (step de 2)
$.order.lineItems[0::2]
// → [{ sku: "WH-1042", ... }, { sku: "SC-PRO-7", ... }]

Expressões de Filtro — Consultando por Condição

É aqui que o JSONPath realmente se destaca. A sintaxe de filtro [?(...)] permite consultar itens de array pelos valores de seus campos. O símbolo @ se refere ao item atual sendo testado. O artigo original de 2007 de Stefan Goessner introduziu essa sintaxe, e o RFC 9535 a formalizou:

text
// Itens abaixo de $50
$.order.lineItems[?(@.unitPrice < 50)]
// → [{ sku: "CB-USB-C", unitPrice: 12.49, ... }, { sku: "SC-PRO-7", unitPrice: 34.00, ... }]

// Apenas itens em estoque
$.order.lineItems[?(@.inStock == true)]
// → [{ sku: "WH-1042", ... }, { sku: "CB-USB-C", ... }]

// Itens com quantidade maior que 1
$.order.lineItems[?(@.qty > 1)]
// → [{ sku: "CB-USB-C", qty: 2, ... }]

// Itens fora de estoque
$.order.lineItems[?(@.inStock == false)].name
// → ["Phone Stand Pro"]

Você também pode combinar condições. A maioria das implementações suporta && e || dentro de expressões de filtro, permitindo escrever coisas como [?(@.inStock == true && @.unitPrice < 30)]. Verifique a documentação da sua biblioteca — o comportamento em relação ao suporte de operadores era uma das principais inconsistências entre implementações pré-RFC que a padronização RFC 9535 buscou corrigir.

Verificando existência de campo: Para filtrar itens que têm um campo específico (independentemente do seu valor), use a sintaxe de verificação de existência: [?(@.fieldName)]. Por exemplo, se alguns itens tivessem um campo discountCode e outros não, $.order.lineItems[?(@.discountCode)] retornaria apenas os que têm desconto.

Usando JSONPath em JavaScript

Ainda não há suporte nativo a JSONPath em JavaScript (ao contrário do XPath, que tem document.evaluate() no navegador). Você precisará de uma biblioteca. As duas mais populares são jsonpath e jsonpath-plus. O pacote jsonpath-plus é mais ativamente mantido e tem melhor alinhamento com o RFC 9535:

bash
npm install jsonpath-plus
js
import { JSONPath } from 'jsonpath-plus';

const order = {
  order: {
    id: 'ORD-9182',
    status: 'shipped',
    customer: { name: 'Maria Chen', email: '[email protected]' },
    lineItems: [
      { sku: 'WH-1042', name: 'Wireless Headphones', qty: 1, unitPrice: 89.99, inStock: true },
      { sku: 'CB-USB-C', name: 'USB-C Charging Cable', qty: 2, unitPrice: 12.49, inStock: true },
      { sku: 'SC-PRO-7', name: 'Phone Stand Pro', qty: 1, unitPrice: 34.00, inStock: false }
    ],
    totals: { subtotal: 148.97, shipping: 5.99, tax: 11.92, total: 166.88 }
  }
};

// Pegar todos os nomes de produtos
const names = JSONPath({ path: '$.order.lineItems[*].name', json: order });
console.log(names);
// [ 'Wireless Headphones', 'USB-C Charging Cable', 'Phone Stand Pro' ]

// Pegar itens em estoque abaixo de $50
const affordable = JSONPath({ path: '$.order.lineItems[?(@.inStock == true && @.unitPrice < 50)]', json: order });
console.log(affordable.map(item => item.name));
// [ 'USB-C Charging Cable' ]

// Pegar o total do pedido
const total = JSONPath({ path: '$.order.totals.total', json: order });
console.log(total[0]); // 166.88

Note que JSONPath() sempre retorna um array — mesmo quando você está consultando um único valor. Isso é por design: uma expressão de caminho é um seletor, e um seletor pode corresponder a zero, um ou muitos nós. Então sempre pegue result[0] quando você sabe que está alvejando um valor único.

Usando JSONPath em Python

Em Python, a biblioteca jsonpath-ng é a opção mais completa. Ela suporta o spec principal mais a maior parte da sintaxe de expressões de filtro:

bash
pip install jsonpath-ng
python
from jsonpath_ng import parse

order = {
    "order": {
        "id": "ORD-9182",
        "status": "shipped",
        "customer": {"name": "Maria Chen", "email": "[email protected]"},
        "lineItems": [
            {"sku": "WH-1042", "name": "Wireless Headphones", "qty": 1, "unitPrice": 89.99, "inStock": True},
            {"sku": "CB-USB-C", "name": "USB-C Charging Cable", "qty": 2, "unitPrice": 12.49, "inStock": True},
            {"sku": "SC-PRO-7", "name": "Phone Stand Pro", "qty": 1, "unitPrice": 34.00, "inStock": False}
        ],
        "totals": {"subtotal": 148.97, "shipping": 5.99, "tax": 11.92, "total": 166.88}
    }
}

# Parse a expressão uma vez, depois reutilize (mais eficiente)
expr = parse("$.order.lineItems[*].name")
names = [match.value for match in expr.find(order)]
print(names)
# ['Wireless Headphones', 'USB-C Charging Cable', 'Phone Stand Pro']

# Pegar todos os itens e verificar estoque
expr2 = parse("$.order.lineItems[*]")
for item in [m.value for m in expr2.find(order)]:
    status = "in stock" if item["inStock"] else "OUT OF STOCK"
    print(f"{item['name']} (${item['unitPrice']}) — {status}")

# Saída:
# Wireless Headphones ($89.99) — in stock
# USB-C Charging Cable ($12.49) — in stock
# Phone Stand Pro ($34.0) — OUT OF STOCK

O padrão parse() + find() é a abordagem correta para uso em produção — compile a expressão uma vez e execute-a em muitos documentos, em vez de reanalisar a string de caminho a cada vez. A documentação do jsonpath-ng no GitHub tem mais detalhes sobre a sintaxe de filtro estendida e o método update() para modificar nós correspondentes.

JSONPath vs jq — Qual Devo Usar?

Às vezes as pessoas confundem JSONPath com jq — ambos "consultam JSON", mas resolvem problemas diferentes. Aqui está a análise prática:

  • JSONPath é uma linguagem de expressão de caminho projetada para ser incorporada em aplicações. Ela consulta e extrai valores. Você a usa dentro de código JavaScript, Python, Java, ou arquivos de configuração como seletores do Kubernetes.
  • jq é um processador de linha de comando completo com sua própria linguagem de programação. Pode consultar, transformar, remodelar, filtrar e calcular novos valores. É a ferramenta certa para scripts de shell e manipulação de dados pontuais em um terminal.
  • Use JSONPath quando estiver escrevendo código de aplicação que precisa extrair campos de JSON programaticamente — especialmente quando a biblioteca precisa ser incorporável e leve.
  • Use jq quando estiver em uma linha de comando, escrevendo scripts de shell, depurando respostas de API, ou precisar fazer transformações (renomear campos, agregar, construir novas estruturas) que vão além da extração simples.
  • A sintaxe do jq é mais poderosa mas também mais complexa — jq '.order.lineItems[] | select(.inStock) | .name' vs o JSONPath $.order.lineItems[?(@.inStock)].name. Para extração pura, JSONPath frequentemente é mais legível.

Há também um meio-termo: se você já está trabalhando em JavaScript e só precisa de transformações ocasionais, algo como _.get() do Lodash cobre o acesso simples a caminhos sem nenhuma dependência. Mas para qualquer coisa envolvendo wildcards, recursão, ou expressões de filtro, uma biblioteca JSONPath adequada vale a pena.

Conclusão

JSONPath preenche uma lacuna real entre "percorrer manualmente este objeto em um for loop" e "iniciar um processo jq". Uma vez que você se familiarize com $, ., [*], .. e [?(filter)], você vai recorrer a ele constantemente — especialmente ao trabalhar com grandes payloads de API onde você só precisa de um punhado de campos. A padronização RFC 9535 significa que a sintaxe agora é estável, então as expressões que você escreve hoje devem funcionar de forma consistente em bibliotecas compatíveis. Experimente suas expressões com a ferramenta JSON Path — cole seu JSON, escreva um caminho e veja os resultados instantaneamente. E se seu JSON precisar de limpeza primeiro, o JSON Formatter e o JSON Validator estão bem ali também.