Um Java DSL para leitura de documentos JSON.
Jayway JsonPath é uma versão Java da implementação de Stefan Goessner JsonPath.
JsonPath está disponível no Repositório Central Maven. Os usuários do Maven adicionam isso ao seu POM.
< dependency >
< groupId >com.jayway.jsonpath</ groupId >
< artifactId >json-path</ artifactId >
< version >2.9.0</ version >
</ dependency >
Se precisar de ajuda, faça perguntas no Stack Overflow. Marque a pergunta como 'jsonpath' e 'java'.
As expressões JsonPath sempre se referem a uma estrutura JSON da mesma forma que as expressões XPath são usadas em combinação com um documento XML. O "objeto membro raiz" em JsonPath é sempre referido como $
independentemente de ser um objeto ou array.
Expressões JsonPath podem usar a notação de ponto
$.store.book[0].title
ou a notação entre colchetes
$['store']['book'][0]['title']
Operador | Descrição |
---|---|
$ | O elemento raiz a ser consultado. Isso inicia todas as expressões de caminho. |
@ | O nó atual que está sendo processado por um predicado de filtro. |
* | Curinga. Disponível em qualquer lugar onde seja necessário um nome ou número. |
.. | Varredura profunda. Disponível em qualquer lugar onde um nome seja necessário. |
.<name> | Filho com notação de ponto |
['<name>' (, '<name>')] | Filho ou filhos notados entre colchetes |
[<number> (, <number>)] | Índice ou índices de array |
[start:end] | Operador de fatia de matriz |
[?(<expression>)] | Expressão de filtro. A expressão deve ser avaliada como um valor booleano. |
As funções podem ser invocadas no final de um caminho - a entrada de uma função é a saída da expressão do caminho. A saída da função é ditada pela própria função.
Função | Descrição | Tipo de saída |
---|---|---|
min() | Fornece o valor mínimo de uma matriz de números | Dobro |
max() | Fornece o valor máximo de uma matriz de números | Dobro |
avg() | Fornece o valor médio de uma matriz de números | Dobro |
stddev() | Fornece o valor do desvio padrão de uma matriz de números | Dobro |
length() | Fornece o comprimento de uma matriz | Inteiro |
sum() | Fornece o valor da soma de uma matriz de números | Dobro |
keys() | Fornece as chaves de propriedade (uma alternativa para o terminal til ~ ) | Set<E> |
concat(X) | Fornece uma versão concatenada da saída do caminho com um novo item | como entrada |
append(X) | adicione um item à matriz de saída do caminho json | como entrada |
first() | Fornece o primeiro item de um array | Depende da matriz |
last() | Fornece o último item de um array | Depende da matriz |
index(X) | Fornece o item de um array de índice: X, se o X for negativo, pega de trás para frente | Depende da matriz |
Filtros são expressões lógicas usadas para filtrar matrizes. Um filtro típico seria [?(@.age > 18)]
onde @
representa o item atual que está sendo processado. Filtros mais complexos podem ser criados com operadores lógicos &&
e ||
. Literais de string devem ser colocados entre aspas simples ou duplas ( [?(@.color == 'blue')]
ou [?(@.color == "blue")]
).
Operador | Descrição |
---|---|
== | esquerda é igual a direita (observe que 1 não é igual a '1') |
!= | esquerda não é igual à direita |
< | esquerda é menor que direita |
<= | esquerda é menor ou igual à direita |
> | esquerda é maior que direita |
>= | esquerda é maior ou igual à direita |
=~ | left corresponde à expressão regular [?(@.name =~ /foo.*?/i)] |
in | esquerda existe na direita [?(@.size in ['S', 'M'])] |
nin | esquerda não existe na direita |
subsetof | esquerda é um subconjunto da direita [?(@.sizes subconjunto de ['S', 'M', 'L'])] |
anyof | esquerda tem uma intersecção com direita [?(@.sizes anyof ['M', 'L'])] |
noneof | esquerda não tem interseção com direita [?(@.sizes noneof ['M', 'L'])] |
size | o tamanho da esquerda (array ou string) deve corresponder à direita |
empty | left (array ou string) deve estar vazio |
Dado o json
{
"store" : {
"book" : [
{
"category" : "reference" ,
"author" : "Nigel Rees" ,
"title" : "Sayings of the Century" ,
"price" : 8.95
} ,
{
"category" : "fiction" ,
"author" : "Evelyn Waugh" ,
"title" : "Sword of Honour" ,
"price" : 12.99
} ,
{
"category" : "fiction" ,
"author" : "Herman Melville" ,
"title" : "Moby Dick" ,
"isbn" : "0-553-21311-3" ,
"price" : 8.99
} ,
{
"category" : "fiction" ,
"author" : "J. R. R. Tolkien" ,
"title" : "The Lord of the Rings" ,
"isbn" : "0-395-19395-8" ,
"price" : 22.99
}
] ,
"bicycle" : {
"color" : "red" ,
"price" : 19.95
}
} ,
"expensive" : 10
}
JsonPath | Resultado |
---|---|
$.store.book[*].author | Os autores de todos os livros |
$..author | Todos os autores |
$.store.* | Todas as coisas, tanto livros quanto bicicletas |
$.store..price | O preço de tudo |
$..book[2] | O terceiro livro |
$..book[-2] | O penúltimo livro |
$..book[0,1] | Os dois primeiros livros |
$..book[:2] | Todos os livros do índice 0 (inclusive) até o índice 2 (exclusivo) |
$..book[1:2] | Todos os livros do índice 1 (inclusive) até o índice 2 (exclusivo) |
$..book[-2:] | Dois últimos livros |
$..book[2:] | Todos os livros do índice 2 (inclusive) ao último |
$..book[?(@.isbn)] | Todos os livros com número ISBN |
$.store.book[?(@.price < 10)] | Todos os livros na loja mais baratos que 10 |
$..book[?(@.price <= $['expensive'])] | Todos os livros da loja que não sejam “caros” |
$..book[?(@.author =~ /.*REES/i)] | Todos os livros que correspondem a regex (ignorar maiúsculas e minúsculas) |
$..* | Me dê tudo |
$..book.length() | O número de livros |
A maneira mais simples e direta de usar JsonPath é por meio da API de leitura estática.
String json = "..." ;
List < String > authors = JsonPath . read ( json , "$.store.book[*].author" );
Se você quiser ler apenas uma vez, tudo bem. Caso você precise ler outro caminho também, este não é o caminho a seguir, pois o documento será analisado toda vez que você chamar JsonPath.read(...). Para evitar o problema, você pode analisar o json primeiro.
String json = "..." ;
Object document = Configuration . defaultConfiguration (). jsonProvider (). parse ( json );
String author0 = JsonPath . read ( document , "$.store.book[0].author" );
String author1 = JsonPath . read ( document , "$.store.book[1].author" );
JsonPath também fornece uma API fluente. Este também é o mais flexível.
String json = "..." ;
ReadContext ctx = JsonPath . parse ( json );
List < String > authorsOfBooksWithISBN = ctx . read ( "$.store.book[?(@.isbn)].author" );
List < Map < String , Object >> expensiveBooks = JsonPath
. using ( configuration )
. parse ( json )
. read ( "$.store.book[?(@.price > 10)]" , List . class );
Ao usar JsonPath em Java, é importante saber que tipo você espera no seu resultado. JsonPath tentará automaticamente converter o resultado no tipo esperado pelo invocador.
//Will throw an java.lang.ClassCastException
List < String > list = JsonPath . parse ( json ). read ( "$.store.book[0].author" );
//Works fine
String author = JsonPath . parse ( json ). read ( "$.store.book[0].author" );
Ao avaliar um caminho você precisa entender o conceito de quando um caminho é definite
. Um caminho é indefinite
se contém:
..
- um operador de varredura profunda?(<expression>)
- uma expressão[<number>, <number> (, <number>)]
- vários índices de array Caminhos Indefinite
sempre retornam uma lista (conforme representado pelo JsonProvider atual).
Por padrão, um mapeador de objetos simples é fornecido pelo MappingProvider SPI. Isso permite especificar o tipo de retorno desejado e o MappingProvider tentará realizar o mapeamento. No exemplo abaixo é demonstrado o mapeamento entre Long
e Date
.
String json = "{ " date_as_long " : 1411455611975}" ;
Date date = JsonPath . parse ( json ). read ( "$['date_as_long']" , Date . class );
Se você configurar JsonPath para usar JacksonMappingProvider
, GsonMappingProvider
ou JakartaJsonProvider
você pode até mapear sua saída JsonPath diretamente em POJOs.
Book book = JsonPath . parse ( json ). read ( "$.store.book[0]" , Book . class );
Para obter informações completas sobre o tipo genérico, use TypeRef.
TypeRef < List < String >> typeRef = new TypeRef < List < String >>() {};
List < String > titles = JsonPath . parse ( JSON_DOCUMENT ). read ( "$.store.book[*].title" , typeRef );
Existem três maneiras diferentes de criar predicados de filtro no JsonPath.
Predicados embutidos são aqueles definidos no caminho.
List < Map < String , Object >> books = JsonPath . parse ( json )
. read ( "$.store.book[?(@.price < 10)]" );
Você pode usar &&
e ||
para combinar vários predicados [?(@.price < 10 && @.category == 'fiction')]
, [?(@.category == 'reference' || @.price > 10)]
.
Você pode usar !
para negar um predicado [?(!(@.price < 10 && @.category == 'fiction'))]
.
Os predicados podem ser criados usando a API Filter conforme mostrado abaixo:
import static com . jayway . jsonpath . JsonPath . parse ;
import static com . jayway . jsonpath . Criteria . where ;
import static com . jayway . jsonpath . Filter . filter ;
...
...
Filter cheapFictionFilter = filter (
where ( "category" ). is ( "fiction" ). and ( "price" ). lte ( 10D )
);
List < Map < String , Object >> books =
parse ( json ). read ( "$.store.book[?]" , cheapFictionFilter );
Observe o espaço reservado ?
para o filtro no caminho. Quando vários filtros são fornecidos, eles são aplicados na ordem em que o número de espaços reservados deve corresponder ao número de filtros fornecidos. Você pode especificar vários espaços reservados de predicado em uma operação de filtro [?, ?]
, ambos os predicados devem corresponder.
Os filtros também podem ser combinados com 'OR' e 'AND'
Filter fooOrBar = filter (
where ( "foo" ). exists ( true )). or ( where ( "bar" ). exists ( true )
);
Filter fooAndBar = filter (
where ( "foo" ). exists ( true )). and ( where ( "bar" ). exists ( true )
);
A terceira opção é implementar seus próprios predicados
Predicate booksWithISBN = new Predicate () {
@ Override
public boolean apply ( PredicateContext ctx ) {
return ctx . item ( Map . class ). containsKey ( "isbn" );
}
};
List < Map < String , Object >> books =
reader . read ( "$.store.book[?].isbn" , List . class , booksWithISBN );
Na implementação de Goessner, um JsonPath pode retornar Path
ou Value
. Value
é o padrão e o que todos os exemplos acima estão retornando. Se você preferir ter o caminho dos elementos que nossa consulta está atingindo, isso pode ser conseguido com uma opção.
Configuration conf = Configuration . builder ()
. options ( Option . AS_PATH_LIST ). build ();
List < String > pathList = using ( conf ). parse ( json ). read ( "$..author" );
assertThat ( pathList ). containsExactly (
"$['store']['book'][0]['author']" ,
"$['store']['book'][1]['author']" ,
"$['store']['book'][2]['author']" ,
"$['store']['book'][3]['author']" );
A biblioteca oferece a possibilidade de definir um valor.
String newJson = JsonPath . parse ( json ). set ( "$['store']['book'][0]['author']" , "Paul" ). jsonString ();
Ao criar sua configuração, existem alguns sinalizadores de opção que podem alterar o comportamento padrão.
DEFAULT_PATH_LEAF_TO_NULL
Esta opção faz com que JsonPath retorne nulo para folhas ausentes. Considere o seguinte json
[
{
"name" : "john" ,
"gender" : "male"
} ,
{
"name" : "ben"
}
]
Configuration conf = Configuration . defaultConfiguration ();
//Works fine
String gender0 = JsonPath . using ( conf ). parse ( json ). read ( "$[0]['gender']" );
//PathNotFoundException thrown
String gender1 = JsonPath . using ( conf ). parse ( json ). read ( "$[1]['gender']" );
Configuration conf2 = conf . addOptions ( Option . DEFAULT_PATH_LEAF_TO_NULL );
//Works fine
String gender0 = JsonPath . using ( conf2 ). parse ( json ). read ( "$[0]['gender']" );
//Works fine (null is returned)
String gender1 = JsonPath . using ( conf2 ). parse ( json ). read ( "$[1]['gender']" );
ALWAYS_RETURN_LIST
Esta opção configura o JsonPath para retornar uma lista mesmo quando o caminho for definite
.
Configuration conf = Configuration . defaultConfiguration ();
//ClassCastException thrown
List < String > genders0 = JsonPath . using ( conf ). parse ( json ). read ( "$[0]['gender']" );
Configuration conf2 = conf . addOptions ( Option . ALWAYS_RETURN_LIST );
//Works fine
List < String > genders0 = JsonPath . using ( conf2 ). parse ( json ). read ( "$[0]['gender']" );
SUPPRESS_EXCEPTIONS
Esta opção garante que nenhuma exceção seja propagada na avaliação do caminho. Segue estas regras simples:
ALWAYS_RETURN_LIST
estiver presente uma lista vazia será retornadaALWAYS_RETURN_LIST
NÃO estiver presente, null será retornado REQUIRE_PROPERTIES
Esta opção configura JsonPath para exigir propriedades definidas em path quando um caminho indefinite
é avaliado.
Configuration conf = Configuration . defaultConfiguration ();
//Works fine
List < String > genders = JsonPath . using ( conf ). parse ( json ). read ( "$[*]['gender']" );
Configuration conf2 = conf . addOptions ( Option . REQUIRE_PROPERTIES );
//PathNotFoundException thrown
List < String > genders = JsonPath . using ( conf2 ). parse ( json ). read ( "$[*]['gender']" );
JsonPath é enviado com cinco JsonProviders diferentes:
A alteração dos padrões de configuração conforme demonstrado só deve ser feita quando seu aplicativo estiver sendo inicializado. Alterações durante o tempo de execução são fortemente desencorajadas, especialmente em aplicativos multithread.
Configuration . setDefaults ( new Configuration . Defaults () {
private final JsonProvider jsonProvider = new JacksonJsonProvider ();
private final MappingProvider mappingProvider = new JacksonMappingProvider ();
@ Override
public JsonProvider jsonProvider () {
return jsonProvider ;
}
@ Override
public MappingProvider mappingProvider () {
return mappingProvider ;
}
@ Override
public Set < Option > options () {
return EnumSet . noneOf ( Option . class );
}
});
Observe que o JacksonJsonProvider requer com.fasterxml.jackson.core:jackson-databind:2.4.5
e o GsonJsonProvider requer com.google.code.gson:gson:2.3.1
em seu caminho de classe.
Ambos os provedores Jakarta EE 9 JSON-P (JSR-342) e JSON-B (JSR-367) esperam pelo menos Java 8 e exigem implementações de API JSON compatíveis (como Eclipse Glassfish e Eclipse Yasson) no caminho de classe do tempo de execução do aplicativo; tais implementações também podem ser fornecidas pelo contêiner do aplicativo Java EE. Observe também que o Apache Johnzon ainda não é compatível com o caminho de classe com a especificação Jakarta EE 9 e, se o provedor de mapeamento JSON-B for escolhido, o provedor JSON-P também deverá ser configurado e usado.
Uma peculiaridade das especificações Jakarta EE 9 para processamento JSON e ligação de dados (mapeamento) é a imutabilidade de matrizes e objetos Json assim que eles são totalmente analisados ou gravados. Para respeitar a especificação da API, mas permitir que JsonPath modifique documentos Json por meio de operações de adição, configuração/colocação, substituição e exclusão, JakartaJsonProvider
deve ser inicializado com o argumento true
opcional:
JsonProvider jsonProvider = new JakartaJsonProvider(true)
(habilita matrizes e objetos Json mutáveis)JsonProvider jsonProvider = new JakartaJsonProvider()
(padrão, estrita conformidade com a API JSON-P)Todas as operações de pesquisa e leitura com JsonPath são suportadas independentemente do modo de inicialização. O modo padrão também precisa de menos memória e tem melhor desempenho.
No JsonPath 2.1.0, um novo Cache SPI foi introduzido. Isso permite que os consumidores da API configurem o cache de caminho de uma maneira que atenda às suas necessidades. O cache deve ser configurado antes de ser acessado pela primeira vez ou antes de uma JsonPathException ser lançada. JsonPath vem com duas implementações de cache
com.jayway.jsonpath.spi.cache.LRUCache
(padrão, thread-safe)com.jayway.jsonpath.spi.cache.NOOPCache
(sem cache)Se você deseja implementar seu próprio cache, a API é simples.
CacheProvider . setCache ( new Cache () {
//Not thread safe simple cache
private Map < String , JsonPath > map = new HashMap < String , JsonPath >();
@ Override
public JsonPath get ( String key ) {
return map . get ( key );
}
@ Override
public void put ( String key , JsonPath jsonPath ) {
map . put ( key , jsonPath );
}
});