Un DSL Java pour lire les documents JSON.
Jayway JsonPath est un port Java de l'implémentation de Stefan Goessner JsonPath.
JsonPath est disponible dans le référentiel Central Maven. Les utilisateurs Maven l'ajoutent à votre POM.
< dependency >
< groupId >com.jayway.jsonpath</ groupId >
< artifactId >json-path</ artifactId >
< version >2.9.0</ version >
</ dependency >
Si vous avez besoin d'aide, posez des questions à Stack Overflow. Marquez la question « jsonpath » et « java ».
Les expressions JsonPath font toujours référence à une structure JSON de la même manière que les expressions XPath sont utilisées en combinaison avec un document XML. L'« objet membre racine » dans JsonPath est toujours appelé $
, qu'il s'agisse d'un objet ou d'un tableau.
Les expressions JsonPath peuvent utiliser la notation par points
$.store.book[0].title
ou la notation entre parenthèses
$['store']['book'][0]['title']
Opérateur | Description |
---|---|
$ | L'élément racine à interroger. Cela démarre toutes les expressions de chemin. |
@ | Le nœud actuel est traité par un prédicat de filtre. |
* | Caractère générique. Disponible partout où un nom ou un chiffre est requis. |
.. | Analyse approfondie. Disponible partout où un nom est requis. |
.<name> | Enfant noté par des points |
['<name>' (, '<name>')] | Enfant(s) noté(s) entre parenthèses |
[<number> (, <number>)] | Index ou index de tableau |
[start:end] | Opérateur de tranche de tableau |
[?(<expression>)] | Filtrer les expressions. L'expression doit être évaluée à une valeur booléenne. |
Les fonctions peuvent être invoquées à la fin d'un chemin - l'entrée d'une fonction est la sortie de l'expression du chemin. La sortie de la fonction est dictée par la fonction elle-même.
Fonction | Description | Type de sortie |
---|---|---|
min() | Fournit la valeur minimale d'un tableau de nombres | Double |
max() | Fournit la valeur maximale d'un tableau de nombres | Double |
avg() | Fournit la valeur moyenne d'un tableau de nombres | Double |
stddev() | Fournit la valeur d’écart type d’un tableau de nombres | Double |
length() | Fournit la longueur d'un tableau | Entier |
sum() | Fournit la valeur de somme d'un tableau de nombres | Double |
keys() | Fournit les clés de propriété (Une alternative au terminal tilde ~ ) | Set<E> |
concat(X) | Fournit une version concaténée de la sortie du chemin avec un nouvel élément | comme entrée |
append(X) | ajouter un élément au tableau de sortie du chemin json | comme entrée |
first() | Fournit le premier élément d'un tableau | Cela dépend du tableau |
last() | Fournit le dernier élément d'un tableau | Cela dépend du tableau |
index(X) | Fournit l'élément d'un tableau d'index : X, si le X est négatif, prendre à partir de l'envers | Cela dépend du tableau |
Les filtres sont des expressions logiques utilisées pour filtrer les tableaux. Un filtre typique serait [?(@.age > 18)]
où @
représente l'élément en cours de traitement. Des filtres plus complexes peuvent être créés avec les opérateurs logiques &&
et ||
. Les littéraux de chaîne doivent être entourés de guillemets simples ou doubles ( [?(@.color == 'blue')]
ou [?(@.color == "blue")]
).
Opérateur | Description |
---|---|
== | la gauche est égale à la droite (notez que 1 n'est pas égal à « 1 ») |
!= | la gauche n'est pas égale à la droite |
< | la gauche est inférieure à la droite |
<= | la gauche est inférieure ou égale à la droite |
> | la gauche est plus grande que la droite |
>= | la gauche est supérieure ou égale à la droite |
=~ | la gauche correspond à l'expression régulière [? (@.name =~ /foo.*?/i)] |
in | la gauche existe à droite [? (@.size in ['S', 'M'])] |
nin | la gauche n'existe pas à droite |
subsetof | la gauche est un sous-ensemble de la droite [? (@.sizes subsetof ['S', 'M', 'L'])] |
anyof | la gauche a une intersection avec la droite [? (@.sizes anyof ['M', 'L'])] |
noneof | la gauche n'a pas d'intersection avec la droite [? (@.sizes noneof ['M', 'L'])] |
size | la taille de la gauche (tableau ou chaîne) doit correspondre à la droite |
empty | left (tableau ou chaîne) doit être vide |
Étant donné le 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 | Résultat |
---|---|
$.store.book[*].author | Les auteurs de tous les livres |
$..author | Tous les auteurs |
$.store.* | Tout, des livres et des vélos |
$.store..price | Le prix de tout |
$..book[2] | Le troisième livre |
$..book[-2] | L'avant-dernier livre |
$..book[0,1] | Les deux premiers livres |
$..book[:2] | Tous les livres de l'index 0 (inclus) jusqu'à l'index 2 (exclusif) |
$..book[1:2] | Tous les livres de l'index 1 (inclus) jusqu'à l'index 2 (exclusif) |
$..book[-2:] | Les deux derniers livres |
$..book[2:] | Tous les livres de l'index 2 (inclus) au dernier |
$..book[?(@.isbn)] | Tous les livres avec un numéro ISBN |
$.store.book[?(@.price < 10)] | Tous les livres en magasin moins cher que 10 |
$..book[?(@.price <= $['expensive'])] | Tous les livres en magasin qui ne sont pas "chers" |
$..book[?(@.author =~ /.*REES/i)] | Tous les livres correspondant à l'expression régulière (ignorer la casse) |
$..* | Donne-moi tout |
$..book.length() | Le nombre de livres |
La manière la plus simple et la plus directe d'utiliser JsonPath consiste à utiliser l'API de lecture statique.
String json = "..." ;
List < String > authors = JsonPath . read ( json , "$.store.book[*].author" );
Si vous ne voulez lire qu'une seule fois, ce n'est pas grave. Dans le cas où vous auriez également besoin de lire un autre chemin, ce n'est pas la voie à suivre puisque le document sera analysé à chaque fois que vous appellerez JsonPath.read(...). Pour éviter le problème, vous pouvez d'abord analyser le json.
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 fournit également une API fluide. C'est aussi le plus flexible.
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 );
Lorsque vous utilisez JsonPath en Java, il est important de savoir quel type vous attendez dans votre résultat. JsonPath tentera automatiquement de convertir le résultat dans le type attendu par l'invocateur.
//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" );
Lors de l'évaluation d'un chemin, vous devez comprendre le concept du moment où un chemin est definite
. Un chemin est indefinite
s'il contient :
..
- un opérateur d'analyse approfondie?(<expression>)
- une expression[<number>, <number> (, <number>)]
- plusieurs index de tableau Les chemins Indefinite
renvoient toujours une liste (telle que représentée par le JsonProvider actuel).
Par défaut, un simple mappeur d’objets est fourni par le MappingProvider SPI. Cela vous permet de spécifier le type de retour souhaité et MappingProvider tentera d'effectuer le mappage. Dans l'exemple ci-dessous, le mappage entre Long
et Date
est démontré.
String json = "{ " date_as_long " : 1411455611975}" ;
Date date = JsonPath . parse ( json ). read ( "$['date_as_long']" , Date . class );
Si vous configurez JsonPath pour utiliser JacksonMappingProvider
, GsonMappingProvider
ou JakartaJsonProvider
, vous pouvez même mapper votre sortie JsonPath directement dans les POJO.
Book book = JsonPath . parse ( json ). read ( "$.store.book[0]" , Book . class );
Pour obtenir des informations complètes sur le type générique, utilisez TypeRef.
TypeRef < List < String >> typeRef = new TypeRef < List < String >>() {};
List < String > titles = JsonPath . parse ( JSON_DOCUMENT ). read ( "$.store.book[*].title" , typeRef );
Il existe trois manières différentes de créer des prédicats de filtre dans JsonPath.
Les prédicats en ligne sont ceux définis dans le chemin.
List < Map < String , Object >> books = JsonPath . parse ( json )
. read ( "$.store.book[?(@.price < 10)]" );
Vous pouvez utiliser &&
et ||
pour combiner plusieurs prédicats [?(@.price < 10 && @.category == 'fiction')]
, [?(@.category == 'reference' || @.price > 10)]
.
Vous pouvez utiliser !
pour nier un prédicat [?(!(@.price < 10 && @.category == 'fiction'))]
.
Les prédicats peuvent être créés à l'aide de l'API Filter comme indiqué ci-dessous :
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 );
Remarquez l'espace réservé ?
pour le filtre dans le chemin. Lorsque plusieurs filtres sont fournis, ils sont appliqués dans l'ordre dans lequel le nombre d'espaces réservés doit correspondre au nombre de filtres fournis. Vous pouvez spécifier plusieurs espaces réservés de prédicat en une seule opération de filtrage [?, ?]
, les deux prédicats doivent correspondre.
Les filtres peuvent également être combinés avec « OU » et « ET ».
Filter fooOrBar = filter (
where ( "foo" ). exists ( true )). or ( where ( "bar" ). exists ( true )
);
Filter fooAndBar = filter (
where ( "foo" ). exists ( true )). and ( where ( "bar" ). exists ( true )
);
La troisième option consiste à implémenter vos propres prédicats
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 );
Dans l'implémentation Goessner, un JsonPath peut renvoyer Path
ou Value
. Value
est la valeur par défaut et ce que renvoient tous les exemples ci-dessus. Si vous préférez connaître le chemin des éléments que notre requête atteint, cela peut être réalisé avec une option.
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']" );
La bibliothèque offre la possibilité de définir une valeur.
String newJson = JsonPath . parse ( json ). set ( "$['store']['book'][0]['author']" , "Paul" ). jsonString ();
Lors de la création de votre configuration, quelques options peuvent modifier le comportement par défaut.
DEFAULT_PATH_LEAF_TO_NULL
Cette option fait que JsonPath renvoie null pour les feuilles manquantes. Considérez le json suivant
[
{
"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
Cette option configure JsonPath pour renvoyer une liste même lorsque le chemin est 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
Cette option garantit qu'aucune exception n'est propagée à partir de l'évaluation du chemin. Il suit ces règles simples :
ALWAYS_RETURN_LIST
est présente, une liste vide sera renvoyéeALWAYS_RETURN_LIST
n'est PAS présente, null est renvoyé REQUIRE_PROPERTIES
Cette option configure JsonPath pour exiger les propriétés définies dans le chemin lorsqu'un chemin indefinite
est évalué.
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 est livré avec cinq JsonProviders différents :
La modification des paramètres de configuration par défaut, comme illustré, ne doit être effectuée que lors de l'initialisation de votre application. Les modifications pendant l'exécution sont fortement déconseillées, en particulier dans les applications 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 );
}
});
Notez que le JacksonJsonProvider nécessite com.fasterxml.jackson.core:jackson-databind:2.4.5
et que le GsonJsonProvider nécessite com.google.code.gson:gson:2.3.1
sur votre chemin de classe.
Les fournisseurs Jakarta EE 9 JSON-P (JSR-342) et JSON-B (JSR-367) attendent au moins Java 8 et nécessitent des implémentations d'API JSON compatibles (telles que Eclipse Glassfish et Eclipse Yasson) sur le chemin de classe d'exécution de l'application ; de telles implémentations peuvent également être fournies par le conteneur d'applications Java EE. Veuillez également noter qu'Apache Johnzon n'est pas encore compatible avec le chemin de classe avec la spécification Jakarta EE 9, et si le fournisseur de mappage JSON-B est choisi, le fournisseur JSON-P doit également être configuré et utilisé.
Une particularité des spécifications Jakarta EE 9 pour le traitement JSON et la liaison de données (mappage) est l'immuabilité des tableaux et objets Json dès qu'ils sont entièrement analysés ou écrits. Pour respecter la spécification de l'API, mais permettre à JsonPath de modifier les documents Json via des opérations d'ajout, de définition/mise, de remplacement et de suppression, JakartaJsonProvider
doit être initialisé avec un argument true
facultatif :
JsonProvider jsonProvider = new JakartaJsonProvider(true)
(activer les tableaux et objets Json mutables)JsonProvider jsonProvider = new JakartaJsonProvider()
(par défaut, stricte conformité de l'API JSON-P)Toutes les opérations de recherche et de lecture avec JsonPath sont prises en charge quel que soit le mode d'initialisation. Le mode par défaut nécessite également moins de mémoire et est plus performant.
Dans JsonPath 2.1.0, un nouveau Cache SPI a été introduit. Cela permet aux consommateurs d'API de configurer la mise en cache des chemins d'une manière qui répond à leurs besoins. Le cache doit être configuré avant son premier accès, sinon une exception JsonPathException est levée. JsonPath est livré avec deux implémentations de cache
com.jayway.jsonpath.spi.cache.LRUCache
(par défaut, thread-safe)com.jayway.jsonpath.spi.cache.NOOPCache
(pas de cache)Si vous souhaitez implémenter votre propre cache, l'API est simple.
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 );
}
});