Introdução ao JsonPath
1. Visão geral
Uma das vantagens do XML é a disponibilidade de processamento - incluindo XPath - que é definido comoW3C standard. Para o JSON, surgiu uma ferramenta semelhante chamada JSONPath.
Este artigo fornecerá umintroduction to Jayway JsonPath, uma implementação Java doJSONPath specification. Ele descreve a instalação, sintaxe, APIs comuns e uma demonstração de casos de uso.
Leitura adicional:
Teste de integração no Spring
Um guia rápido para escrever testes de integração para um aplicativo Web Spring.
O HttpMediaTypeNotAcceptableException no Spring MVC
Aprenda a lidar com a HttpMediaTypeNotAcceptableException no Spring.
Formatos de dados binários em uma API REST do Spring
Neste artigo, exploraremos como configurar o mecanismo Spring REST para utilizar formatos de dados binários que ilustramos com o Kryo. Além disso, mostramos como oferecer suporte a vários formatos de dados com os buffers do Google Protocol.
2. Configuração
Para usar o JsonPath, basta incluir uma dependência no pom do Maven:
com.jayway.jsonpath
json-path
2.4.0
3. Sintaxe
A seguinte estrutura JSON será usada nesta seção para demonstrar a sintaxe e as APIs do JsonPath:
{
"tool":
{
"jsonpath":
{
"creator":
{
"name": "Jayway Inc.",
"location":
[
"Malmo",
"San Francisco",
"Helsingborg"
]
}
}
},
"book":
[
{
"title": "Beginning JSON",
"price": 49.99
},
{
"title": "JSON at Work",
"price": 29.99
}
]
}
3.1. Notação
JsonPath usa notação especial para representar nós e suas conexões com nós adjacentes em um caminho JsonPath. Existem dois estilos de notação, ou seja, ponto e colchete.
Ambos os caminhos a seguir referem-se ao mesmo nó do documento JSON acima, que é o terceiro elemento dentro do campolocation do nócreator, que é um filho do objetojsonpath pertencente atool no nó raiz.
Com notação de ponto:
$.tool.jsonpath.creator.location[2]
Com notação de suporte:
$['tool']['jsonpath']['creator']['location'][2]
O cifrão ($) representa o objeto do membro raiz.
3.2. Operadores
Temos vários operadores úteis no JsonPath:
Root node ($): este símbolo denota o membro raiz de uma estrutura JSON, independentemente de ser um objeto ou matriz. Seus exemplos de uso foram incluídos na subseção anterior.
Current node (@): representa o nó que está sendo processado, usado principalmente como parte de expressões de entrada para predicados. Suponha que estamos lidando com a matrizbook no documento JSON acima, a expressãobook[?(@.price == 49.99)] se refere ao primeirobook nessa matriz.
Wildcard (): Expresses all elements within the specified scope. For instance, book[] indica todos os nós dentro de uma matrizbook.
3.3. Funções e Filtros
JsonPath também tem funções que podem ser usadas no final de um caminho para sintetizar as expressões de saída desse caminho:min(),max(),avg(),stddev(),length() .
Finalmente - nós temos filtros; são expressões booleanas para restringir as listas de nós retornadas apenas àquelas necessárias para os métodos de chamada.
Alguns exemplos são igualdade (==), correspondência de expressão regular (=~), inclusão (in), verificação de vazio (empty). Os filtros são usados principalmente para predicados.
Para uma lista completa e explicações detalhadas dos diferentes operadores, funções e filtros, consulte o projetoJsonPath GitHub.
4. Operações
Antes de entrarmos em operações, uma observação rápida - esta seção faz uso da estrutura de exemplo JSON que definimos anteriormente.
4.1. Acesso a Documentos
JsonPath tem uma maneira conveniente de acessar documentos JSON, que é por meio de APIsread estáticas:
T JsonPath.read(String jsonString, String jsonPath, Predicate... filters);
As APIsread podem funcionar com APIs fluentes estáticas para fornecer mais flexibilidade:
T JsonPath.parse(String jsonString).read(String jsonPath, Predicate... filters);
Outras variantes sobrecarregadas deread podem ser usadas para diferentes tipos de fontes JSON, incluindoObject,InputStream,URL eFile.
Para simplificar, o teste desta parte não inclui predicados na lista de parâmetros (varargs vazio); predicates será discutido nas subseções posteriores.
Vamos começar definindo dois caminhos de amostra para trabalhar:
String jsonpathCreatorNamePath = "$['tool']['jsonpath']['creator']['name']";
String jsonpathCreatorLocationPath = "$['tool']['jsonpath']['creator']['location'][*]";
A seguir, criaremos um objetoDocumentContext analisando a fonte JSONjsonDataSourceString fornecida. O objeto recém-criado será usado para ler o conteúdo usando os caminhos definidos acima:
DocumentContext jsonContext = JsonPath.parse(jsonDataSourceString);
String jsonpathCreatorName = jsonContext.read(jsonpathCreatorNamePath);
List jsonpathCreatorLocation = jsonContext.read(jsonpathCreatorLocationPath);
A primeira APIread retorna umString contendo o nome do criador JsonPath, enquanto a segunda retorna uma lista de seus endereços. E usaremos a API JUnitAssert para confirmar se os métodos funcionam conforme o esperado:
assertEquals("Jayway Inc.", jsonpathCreatorName);
assertThat(jsonpathCreatorLocation.toString(), containsString("Malmo"));
assertThat(jsonpathCreatorLocation.toString(), containsString("San Francisco"));
assertThat(jsonpathCreatorLocation.toString(), containsString("Helsingborg"));
4.2. Predicados
Agora que concluímos o básico, vamos definir um novo exemplo JSON para trabalhar e ilustrar a criação e o uso de predicados:
{
"book":
[
{
"title": "Beginning JSON",
"author": "Ben Smith",
"price": 49.99
},
{
"title": "JSON at Work",
"author": "Tom Marrs",
"price": 29.99
},
{
"title": "Learn JSON in a DAY",
"author": "Acodemy",
"price": 8.99
},
{
"title": "JSON: Questions and Answers",
"author": "George Duckett",
"price": 6.00
}
],
"price range":
{
"cheap": 10.00,
"medium": 20.00
}
}
Os predicados determinam valores de entrada verdadeiros ou falsos para os filtros restringirem as listas retornadas apenas aos objetos ou matrizes correspondentes. UmPredicate pode ser facilmente integrado em umFilter usando como argumento para seu método de fábrica estático. O conteúdo solicitado pode ser lido em uma string JSON usando esseFilter:
Filter expensiveFilter = Filter.filter(Criteria.where("price").gt(20.00));
List
Também podemos definir nossoPredicate personalizado e usá-lo como um argumento para a APIread:
Predicate expensivePredicate = new Predicate() {
public boolean apply(PredicateContext context) {
String value = context.item(Map.class).get("price").toString();
return Float.valueOf(value) > 20.00;
}
};
List
Finalmente, um predicado pode ser aplicado diretamente à APIread sem a criação de nenhum objeto, o que é chamado de predicado inline:
List
Todos os três exemplos dePredicate acima são verificados com a ajuda do seguinte método auxiliar de asserção:
private void predicateUsageAssertionHelper(List> predicate) {
assertThat(predicate.toString(), containsString("Beginning JSON"));
assertThat(predicate.toString(), containsString("JSON at Work"));
assertThat(predicate.toString(), not(containsString("Learn JSON in a DAY")));
assertThat(predicate.toString(), not(containsString("JSON: Questions and Answers")));
}
5. Configuração
5.1. Opções
Jayway JsonPath fornece várias opções para ajustar a configuração padrão:
-
Option.AS_PATH_LIST: Retorna caminhos dos acertos de avaliação em vez de seus valores.
-
Option.DEFAULT_PATH_LEAF_TO_NULL: Retorna nulo para folhas ausentes.
-
Option.ALWAYS_RETURN_LIST: Retorna uma lista mesmo quando o caminho é definido.
-
Option.SUPPRESS_EXCEPTIONS: Garante que nenhuma exceção seja propagada da avaliação do caminho.
-
Option.REQUIRE_PROPERTIES: requer propriedades definidas no caminho quando um caminho indefinido é avaliado.
Aqui está comoOption é aplicado do zero:
Configuration configuration = Configuration.builder().options(Option.
e como adicioná-lo a uma configuração existente:
Configuration newConfiguration = configuration.addOptions(Option.
5.2. SPIs
A configuração padrão do JsonPath com a ajuda deOption deve ser suficiente para a maioria das tarefas. No entanto, usuários com casos de uso mais complexos podem modificar o comportamento do JsonPath de acordo com seus requisitos específicos - usando três SPIs diferentes:
-
JsonProvider SPI: permite-nos mudar a forma como o JsonPath analisa e trata os documentos JSON
-
MappingProvider SPI: Permite a personalização de ligações entre valores de nós e tipos de objetos retornados
-
CacheProvider SPI: ajusta as maneiras como os caminhos são armazenados em cache, o que pode ajudar a aumentar o desempenho
6. Um exemplo de casos de uso
Agora que temos um bom entendimento da funcionalidade para a qual o JsonPath pode ser usado - vejamos um exemplo.
Esta seção ilustra como lidar com dados JSON retornados de um serviço da Web - suponha que tenhamos um serviço de informações sobre filmes, que retorna a seguinte estrutura:
[
{
"id": 1,
"title": "Casino Royale",
"director": "Martin Campbell",
"starring":
[
"Daniel Craig",
"Eva Green"
],
"desc": "Twenty-first James Bond movie",
"release date": 1163466000000,
"box office": 594275385
},
{
"id": 2,
"title": "Quantum of Solace",
"director": "Marc Forster",
"starring":
[
"Daniel Craig",
"Olga Kurylenko"
],
"desc": "Twenty-second James Bond movie",
"release date": 1225242000000,
"box office": 591692078
},
{
"id": 3,
"title": "Skyfall",
"director": "Sam Mendes",
"starring":
[
"Daniel Craig",
"Naomie Harris"
],
"desc": "Twenty-third James Bond movie",
"release date": 1350954000000,
"box office": 1110526981
},
{
"id": 4,
"title": "Spectre",
"director": "Sam Mendes",
"starring":
[
"Daniel Craig",
"Lea Seydoux"
],
"desc": "Twenty-fourth James Bond movie",
"release date": 1445821200000,
"box office": 879376275
}
]
Onde o valor do camporelease date é a duração desde a época em milissegundos ebox office é a receita de um filme no cinema em dólares americanos.
Vamos lidar com cinco cenários de trabalho diferentes relacionados a solicitações GET, supondo que a hierarquia JSON acima foi extraída e armazenada em uma variávelString chamadajsonString.
6.1. Obtendo IDs dados de objeto
Nesse caso de uso, um cliente solicita informações detalhadas sobre um filme específico, fornecendo ao servidor oid exato daquele filme. Este exemplo demonstra como o servidor procura dados solicitados antes de retornar ao cliente.
Digamos que precisamos encontrar um registro comid igual a 2. Abaixo está como o processo é implementado e testado.
A primeira etapa é pegar o objeto de dados correto:
Object dataObject = JsonPath.parse(jsonString).read("$[?(@.id == 2)]");
String dataString = dataObject.toString();
A API JUnitAssert confirma a existência de vários campos:
assertThat(dataString, containsString("2"));
assertThat(dataString, containsString("Quantum of Solace"));
assertThat(dataString, containsString("Twenty-second James Bond movie"));
6.2. Estrelando o título do filme
Digamos que queremos um filme estrelado por uma atriz chamadaEva Green. O servidor precisa retornartitle do filme queEva Green está incluído na matrizstarring.
O teste seguinte ilustrará como fazer isso e validará o resultado retornado:
@Test
public void givenStarring_whenRequestingMovieTitle_thenSucceed() {
List
6.3. Cálculo da Receita Total
Este cenário usa uma função JsonPath chamadalength() para descobrir o número de registros do filme, para calcular a receita total de todos os filmes. A implementação e o teste são demonstrados da seguinte maneira:
@Test
public void givenCompleteStructure_whenCalculatingTotalRevenue_thenSucceed() {
DocumentContext context = JsonPath.parse(jsonString);
int length = context.read("$.length()");
long revenue = 0;
for (int i = 0; i < length; i++) {
revenue += context.read("$[" + i + "]['box office']", Long.class);
}
assertEquals(594275385L + 591692078L + 1110526981L + 879376275L, revenue);
}
6.4. Filme de maior receita
Este caso de uso exemplifica o uso de uma opção de configuração JsonPath não padrão, a saberOption.AS_PATH_LIST, para descobrir o filme com a maior receita. As etapas específicas são descritas abaixo.
Inicialmente, precisamos extrair uma lista de todas as receitas das bilheterias dos filmes e convertê-las em uma matriz para classificação:
DocumentContext context = JsonPath.parse(jsonString);
List
A variávelhighestRevenue pode ser facilmente obtida da matriz classificadarevenueArray e usada para calcular o caminho para o registro do filme com a receita mais alta:
int highestRevenue = revenueArray[revenueArray.length - 1];
Configuration pathConfiguration =
Configuration.builder().options(Option.AS_PATH_LIST).build();
List pathList = JsonPath.using(pathConfiguration).parse(jsonString)
.read("$[?(@['box office'] == " + highestRevenue + ")]");
Com base nesse caminho calculado,title do filme correspondente pode ser determinado e retornado:
Map dataRecord = context.read(pathList.get(0));
String title = dataRecord.get("title");
Todo o processo é verificado pela APIAssert:
assertEquals("Skyfall", title);
6.5. Último filme de um diretor
Este exemplo ilustrará a maneira de descobrir o último filme dirigido por um diretor chamadoSam Mendes.
Para começar, é criada uma lista de todos os filmes dirigidos porSam Mendes:
DocumentContext context = JsonPath.parse(jsonString);
List
Essa lista é usada para extração de datas de lançamento. Essas datas serão armazenadas em uma matriz e, em seguida, classificadas:
List
A variávellastestTime, que é o último elemento da matriz classificada, é usada em combinação com o valor do campodirector para determinar otitle do filme solicitado:
long latestTime = dateArray[dateArray.length - 1];
List
A seguinte afirmação provou que tudo funciona como esperado:
assertEquals("Spectre", title);
7. Conclusão
Este tutorial abordou recursos fundamentais do Jayway JsonPath - uma ferramenta poderosa para percorrer e analisar documentos JSON.
Embora o JsonPath tenha algumas desvantagens, como a falta de operadores para alcançar os nós pai ou irmão, ele pode ser altamente útil em muitos cenários.
A implementação de todos esses exemplos e trechos de código pode ser encontrada emGitHub project.