Testador de JSONPath

Teste consultas JSONPath (RFC 9535) em JSON: seletores, filtros, as cinco funções e o caminho normalizado de cada correspondência — no navegador.

Consulta JSONPath
JSON
Correspondências: 2
  • Caminho$['store']['book'][0]['title']
    "Sayings of the Century"
  • Caminho$['store']['book'][2]['title']
    "Moby Dick"

Uma linguagem de consulta que enfim tem um padrão

JSONPath está para JSON como XPath está para XML: uma pequena linguagem para apontar partes de um documento. Você escreve uma expressão como $.store.book[0].title e ela seleciona os nós correspondentes. A ideia vem de um post de blog de Stefan Goessner de 2007, e por dezessete anos esse post foi a única referência — o que significou que cada biblioteca preencheu as lacunas de forma diferente. O que um $.. sozinho seleciona? $[1,2] volta garantidamente em ordem? Como um filtro compara um valor que não está lá? Pergunte a três bibliotecas JSONPath e você pode obter três respostas.

RFC 9535, publicado em 2024, enfim fixou tudo. Este testador mira nesse padrão, não no folclore. Ele executa a consulta como diz a RFC, mostra cada correspondência junto com sua localização exata e — importante — rejeita uma consulta válida em algum dialeto mais antigo mas não em RFC 9535, dizendo onde está o problema em vez de fazer em silêncio algo não padronizado que outra ferramenta faria de outro modo.

Segmentos e seletores

Uma consulta é o identificador raiz $ seguido de uma sequência de segmentos, e cada segmento aplica um ou mais seletores aos nós que recebe. Há cinco seletores:

  • Nome — $.store ou $["store"] seleciona o valor de um membro. A forma com ponto é uma abreviação; a forma de colchetes e aspas funciona para qualquer chave, inclusive uma com espaços ou pontuação.
  • Curinga — * seleciona todos os membros de um objeto ou todos os elementos de um array.
  • Índice — [0] seleciona um elemento de array, e um índice negativo conta a partir do fim, então [-1] é o último.
  • Fatia (slice) — [start:end:step] seleciona um intervalo, exatamente como em Python: [1:3] são os elementos 1 e 2, [::-1] inverte, [::2] pega um a cada dois.
  • Filtro — [?<expressão>] mantém apenas os elementos ou membros para os quais a expressão é verdadeira (explicado abaixo).

Um colchete pode conter vários seletores de uma vez: [0, 2, "title"] seleciona três coisas em um único segmento. E um segmento pode ser um segmento filho (um ponto ou colchete simples) ou um segmento descendente (..), que busca no nó e em todos os seus descendentes — $..author encontra cada author em qualquer parte do documento.

Filtros, e as quatro formas de comparar o nada

Um seletor de filtro testa cada elemento com uma expressão lógica, na qual @ se refere ao elemento atual e $ ao documento inteiro. A expressão pode comparar valores (==, !=, <, <=, >, >=), combinar testes com && e || e !, e simplesmente testar a existência: $..book[[email protected]] mantém os livros que têm um ISBN, porque @.isbn seleciona um nó apenas quando a chave está presente.

A parte sutil é comparar algo que não está lá. Uma consulta à esquerda ou à direita de uma comparação produz um valor ou nada. A RFC define isso com precisão: nada é igual a nada, nada não é igual a nenhum valor real, e qualquer teste de ordem (<, >) contra nada é simplesmente falso. Assim @.price < 10 pula em silêncio um elemento que não tem price em vez de dar erro — o que costuma ser o que você quer, e sempre o que o padrão diz.

As cinco funções

RFC 9535 adiciona cinco funções que você pode chamar dentro de um filtro:

  • length() — o comprimento de uma string (contado em caracteres), um array ou um objeto.
  • count() — quantos nós uma consulta seleciona, para você filtrar por cardinalidade: [?count(@.chapters) > 3].
  • value() — o único valor que uma consulta seleciona, ou nada se seleciona zero ou muitos.
  • match() — se uma string corresponde inteiramente a uma expressão regular.
  • search() — se uma expressão regular é encontrada em qualquer parte de uma string.

O padrão é rigoroso sobre como elas são usadas, e este testador impõe isso ao analisar: length() recebe exatamente um valor, então length(@.*) — uma consulta que poderia selecionar muitos nós — é um erro de sintaxe, não uma consulta que se comporta mal em silêncio. Da mesma forma match() devolve verdadeiro ou falso, então escrever match(@.a, "x") == true é rejeitado, porque um resultado lógico não é algo que se compara.

As expressões regulares são I-Regexp

match() e search() não usam as expressões regulares do JavaScript; usam I-Regexp (RFC 9485), um pequeno subconjunto portátil projetado para se comportar igual em todas as linguagens. A maior parte é o que você esperaria — classes de caracteres, quantificadores, alternância, propriedades Unicode como \p{Lu} para uma letra maiúscula. A única armadilha é o ponto: em I-Regexp, . corresponde a qualquer caractere exceto um retorno de carro ou uma quebra de linha, o que significa que ele corresponde sim aos separadores de linha Unicode U+2028 e U+2029 que um ponto do JavaScript exclui. Este testador compila I-Regexp fielmente, de modo que um padrão se comporta aqui como um servidor conforme o avaliaria.

A diferença entre as duas funções é apenas a ancoragem: match exige que a string inteira corresponda, enquanto search procura o padrão em qualquer parte dela. match(@, "a.*") aceita "abc"; search(@, "b") aceita qualquer string que contenha um b.

Caminhos normalizados, e execução no navegador

Para cada correspondência, este testador mostra um Normalized Path — a localização canônica que a RFC define, escrita na forma de colchetes e aspas: $['store']['book'][0]['author']. Diferente da consulta, que pode selecionar muitos nós, um caminho normalizado aponta para exatamente um, usando apenas seletores de nome e índice e um estilo de aspas fixo. É a resposta a "de onde veio esta correspondência", e é o que permite converter um resultado de curinga de volta em um conjunto de localizações concretas. A maioria dos testadores mostra os valores e deixa você deduzir os caminhos; este mostra ambos.

O motor inteiro roda no seu navegador — o JSON é analisado e a consulta avaliada no seu próprio dispositivo, e nada do que você cola é enviado, armazenado ou registrado. Ele é validado contra a suíte oficial de testes de conformidade do JSONPath, então suas respostas concordam com o padrão e não com a interpretação de uma única biblioteca.

Perguntas frequentes

Qual dialeto de JSONPath isto usa?
RFC 9535, o padrão IETF de 2024, e é validado contra a suíte oficial de testes de conformidade do JSONPath. Ele deliberadamente não aceita as antigas convenções de Goessner onde divergem da RFC; uma consulta assim é rejeitada com a posição do problema para que você possa corrigi-la.
Por que minha consulta foi rejeitada se funciona em outra ferramenta?
Porque aquela ferramenta segue as convenções de Goessner anteriores ao padrão, que diferem de RFC 9535 em vários pontos — um $.. sozinho, expressões de script como [(@.length-1)], índices com zero à esquerda e nomes sem aspas com caracteres especiais são todos não padronizados. A RFC os substituiu por equivalentes bem definidos, e este testador se atém à RFC.
O que é um caminho normalizado?
A localização canônica de um único nó, escrita na forma de colchetes e aspas que a RFC define, p. ex. $['store']['book'][0]['author']. Uma consulta pode corresponder a muitos nós; cada correspondência tem exatamente um caminho normalizado, por isso o testador o mostra ao lado de cada valor.
Como um filtro trata um valor ausente?
Como nada, com regras que a RFC fixa: nada é igual a nada, nada não é igual a nenhum valor real, e qualquer comparação de ordem (<, >) que envolva nada é falsa. Assim @.price < 10 simplesmente pula um elemento sem price em vez de lançar um erro.
match() e search() usam as expressões regulares do JavaScript?
Não. Usam I-Regexp (RFC 9485), um subconjunto portátil. A principal diferença prática é o ponto, que corresponde a tudo exceto retorno de carro e quebra de linha — incluindo U+2028 e U+2029, que um ponto do JavaScript exclui. Este testador compila I-Regexp fielmente para que os resultados correspondam a uma implementação conforme.
Qual é a diferença entre match e search?
A ancoragem. match() exige que a string inteira corresponda ao padrão, como se estivesse entre âncoras; search() tem êxito se o padrão for encontrado em qualquer parte da string. Todo o resto é idêntico.
Meu JSON é enviado a um servidor?
Não. O documento é analisado e a consulta avaliada inteiramente no seu navegador, e nada do que você cola é enviado ou registrado. É seguro testar contra uma resposta de API real ou um arquivo de configuração.