Java DSL для чтения документов JSON.
Jayway JsonPath — это Java-порт реализации Стефана Гесснера JsonPath.
JsonPath доступен в центральном репозитории Maven. Пользователи Maven добавляют это в ваш POM.
< dependency >
< groupId >com.jayway.jsonpath</ groupId >
< artifactId >json-path</ artifactId >
< version >2.9.0</ version >
</ dependency >
Если вам нужна помощь, задавайте вопросы в Stack Overflow. Отметьте вопрос «jsonpath» и «java».
Выражения JsonPath всегда ссылаются на структуру JSON точно так же, как выражение XPath используется в сочетании с XML-документом. «Корневой объект-член» в JsonPath всегда обозначается как $
независимо от того, является ли он объектом или массивом.
Выражения JsonPath могут использовать точечную нотацию.
$.store.book[0].title
или скобка-обозначение
$['store']['book'][0]['title']
Оператор | Описание |
---|---|
$ | Корневой элемент для запроса. Это запускает все выражения пути. |
@ | Текущий узел обрабатывается предикатом фильтра. |
* | Подстановочный знак. Доступно везде, где требуется имя или число. |
.. | Глубокое сканирование. Доступно везде, где требуется имя. |
.<name> | Ребенок, отмеченный точкой |
['<name>' (, '<name>')] | Ребенок или дети, отмеченные скобками |
[<number> (, <number>)] | Индекс массива или индексы |
[start:end] | Оператор среза массива |
[?(<expression>)] | Выражение фильтра. Выражение должно иметь логическое значение. |
Функции можно вызывать в конце пути — входные данные функции являются выходными данными выражения пути. Вывод функции определяется самой функцией.
Функция | Описание | Тип выхода |
---|---|---|
min() | Предоставляет минимальное значение массива чисел | Двойной |
max() | Предоставляет максимальное значение массива чисел | Двойной |
avg() | Предоставляет среднее значение массива чисел | Двойной |
stddev() | Предоставляет значение стандартного отклонения массива чисел. | Двойной |
length() | Предоставляет длину массива | Целое число |
sum() | Предоставляет значение суммы массива чисел | Двойной |
keys() | Предоставляет ключи свойств (альтернатива терминальной тильде ~ ). | Set<E> |
concat(X) | Предоставляет объединенную версию вывода пути с новым элементом. | как ввод |
append(X) | добавить элемент в выходной массив пути json | как ввод |
first() | Предоставляет первый элемент массива | Зависит от массива |
last() | Предоставляет последний элемент массива | Зависит от массива |
index(X) | Предоставляет элемент массива с индексом: X, если X отрицательный, брать наоборот. | Зависит от массива |
Фильтры — это логические выражения, используемые для фильтрации массивов. Типичным фильтром будет [?(@.age > 18)]
, где @
представляет текущий обрабатываемый элемент. Более сложные фильтры можно создавать с помощью логических операторов &&
и ||
. Строковые литералы должны быть заключены в одинарные или двойные кавычки ( [?(@.color == 'blue')]
или [?(@.color == "blue")]
] .
Оператор | Описание |
---|---|
== | лево равно правому (обратите внимание, что 1 не равно «1») |
!= | левое не равно правому |
< | лево меньше правого |
<= | левое меньше или равно правому |
> | левое больше правого |
>= | левое больше или равно правому |
=~ | left соответствует регулярному выражению [?(@.name =~ /foo.*?/i)] |
in | левый существует в правом [?(@.size in ['S', 'M'])] |
nin | левого не существует в правом |
subsetof | left является подмножеством right [?(@.sizes subsetof ['S', 'M', 'L'])] |
anyof | левое пересекается с правым [?(@.sizes Anyof ['M', 'L'])] |
noneof | левое не пересекается с правым [?(@.sizes noneof ['M', 'L'])] |
size | размер левого (массива или строки) должен совпадать с правым |
empty | left (массив или строка) должно быть пустым |
Учитывая 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 | Результат |
---|---|
$.store.book[*].author | Авторы всех книг |
$..author | Все авторы |
$.store.* | Все вещи, и книги, и велосипеды |
$.store..price | Цена всего |
$..book[2] | Третья книга |
$..book[-2] | Предпоследняя книга |
$..book[0,1] | Первые две книги |
$..book[:2] | Все книги с индексом 0 (включительно) до индекса 2 (эксклюзивно) |
$..book[1:2] | Все книги с индекса 1 (включительно) до индекса 2 (эксклюзивно) |
$..book[-2:] | Последние две книги |
$..book[2:] | Все книги от индекса 2 (включительно) до последнего. |
$..book[?(@.isbn)] | Все книги с номером ISBN |
$.store.book[?(@.price < 10)] | Все книги в магазине дешевле 10 |
$..book[?(@.price <= $['expensive'])] | Все книги в магазине не "дорогие" |
$..book[?(@.author =~ /.*REES/i)] | Все книги, соответствующие регулярному выражению (игнорировать регистр) |
$..* | Дай мне все |
$..book.length() | Количество книг |
Самый простой и понятный способ использования JsonPath — через API статического чтения.
String json = "..." ;
List < String > authors = JsonPath . read ( json , "$.store.book[*].author" );
Если вы хотите прочитать только один раз, это нормально. Если вам нужно прочитать и другой путь, это не лучший вариант, поскольку документ будет анализироваться каждый раз, когда вы вызываете JsonPath.read(...). Чтобы избежать проблемы, вы можете сначала проанализировать 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 также предоставляет гибкий API. Это также самый гибкий вариант.
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 );
При использовании JsonPath в Java важно знать, какой тип результата вы ожидаете. JsonPath автоматически попытается привести результат к типу, ожидаемому вызывающей стороной.
//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" );
При оценке пути вам необходимо понимать концепцию того, что путь definite
. Путь является indefinite
, если он содержит:
..
- оператор глубокого сканирования?(<expression>)
- выражение[<number>, <number> (, <number>)]
- несколько индексов массива Indefinite
пути всегда возвращают список (представленный текущим JsonProvider).
По умолчанию простой преобразователь объектов предоставляется SPI MappingProvider. Это позволяет вам указать желаемый тип возвращаемого значения, и MappingProvider попытается выполнить сопоставление. В приведенном ниже примере показано сопоставление между Long
и Date
.
String json = "{ " date_as_long " : 1411455611975}" ;
Date date = JsonPath . parse ( json ). read ( "$['date_as_long']" , Date . class );
Если вы настроите JsonPath для использования JacksonMappingProvider
, GsonMappingProvider
или JakartaJsonProvider
, вы даже сможете сопоставить выходные данные JsonPath непосредственно с POJO.
Book book = JsonPath . parse ( json ). read ( "$.store.book[0]" , Book . class );
Чтобы получить полную информацию о типах дженериков, используйте TypeRef.
TypeRef < List < String >> typeRef = new TypeRef < List < String >>() {};
List < String > titles = JsonPath . parse ( JSON_DOCUMENT ). read ( "$.store.book[*].title" , typeRef );
Существует три разных способа создания предикатов фильтра в JsonPath.
Встроенные предикаты — это те, которые определены в пути.
List < Map < String , Object >> books = JsonPath . parse ( json )
. read ( "$.store.book[?(@.price < 10)]" );
Вы можете использовать &&
и ||
для объединения нескольких предикатов [?(@.price < 10 && @.category == 'fiction')]
, [?(@.category == 'reference' || @.price > 10)]
.
Вы можете использовать !
чтобы отрицать предикат [?(!(@.price < 10 && @.category == 'fiction'))]
.
Предикаты можно создавать с помощью API фильтра, как показано ниже:
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 );
Обратите внимание на заполнитель ?
для фильтра в пути. Если предусмотрено несколько фильтров, они применяются в том порядке, в котором количество заполнителей должно соответствовать количеству предоставленных фильтров. Вы можете указать несколько заполнителей предикатов в одной операции фильтра [?, ?]
, оба предиката должны совпадать.
Фильтры также можно комбинировать с помощью операторов «ИЛИ» и «И».
Filter fooOrBar = filter (
where ( "foo" ). exists ( true )). or ( where ( "bar" ). exists ( true )
);
Filter fooAndBar = filter (
where ( "foo" ). exists ( true )). and ( where ( "bar" ). exists ( true )
);
Третий вариант — реализовать свои собственные предикаты.
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 );
В реализации Goessner JsonPath может возвращать либо Path
, либо Value
. Value
— это значение по умолчанию, которое возвращают все приведенные выше примеры. Если вы предпочитаете указать путь к элементам, с которыми сталкивается наш запрос, этого можно добиться с помощью опции.
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']" );
Библиотека предлагает возможность установить значение.
String newJson = JsonPath . parse ( json ). set ( "$['store']['book'][0]['author']" , "Paul" ). jsonString ();
При создании конфигурации есть несколько флагов опций, которые могут изменить поведение по умолчанию.
DEFAULT_PATH_LEAF_TO_NULL
Эта опция заставляет JsonPath возвращать значение null для отсутствующих листов. Рассмотрим следующий 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
Эта опция настраивает JsonPath на возврат списка, даже если путь 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']" );
ПОДАВЛЕНИЕ_ИСКЛЮЧЕНИЙ
Этот параметр гарантирует, что при оценке пути не будут распространяться исключения. Он следует этим простым правилам:
ALWAYS_RETURN_LIST
, будет возвращен пустой список.ALWAYS_RETURN_LIST
НЕ присутствует, возвращается ноль. REQUIRE_PROPERTIES
Этот параметр настраивает JsonPath так, чтобы он требовал свойств, определенных в пути, при оценке indefinite
пути.
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 поставляется с пятью различными JsonProviders:
Изменение настроек конфигурации по умолчанию, как показано, следует выполнять только во время инициализации вашего приложения. Вносить изменения во время выполнения настоятельно не рекомендуется, особенно в многопоточных приложениях.
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 );
}
});
Обратите внимание, что для JacksonJsonProvider требуется com.fasterxml.jackson.core:jackson-databind:2.4.5
, а для GsonJsonProvider требуется com.google.code.gson:gson:2.3.1
в вашем пути к классам.
Оба поставщика Jakarta EE 9 JSON-P (JSR-342) и JSON-B (JSR-367) ожидают как минимум Java 8 и требуют совместимых реализаций API JSON (таких как Eclipse Glassfish и Eclipse Yasson) в пути к классам среды выполнения приложения; такие реализации также могут предоставляться контейнером приложений Java EE. Также обратите внимание, что Apache Johnzon пока не совместим по пути к классам со спецификацией Jakarta EE 9, и если выбран поставщик сопоставления JSON-B, то поставщик JSON-P также должен быть настроен и использован.
Одной из особенностей спецификаций Jakarta EE 9 для обработки и привязки данных (сопоставления) JSON является неизменяемость массивов и объектов Json сразу после их полного анализа или записи. Чтобы соблюдать спецификацию API, но позволить JsonPath изменять документы Json посредством операций добавления, установки/вставки, замены и удаления, JakartaJsonProvider
должен быть инициализирован с необязательным аргументом true
:
JsonProvider jsonProvider = new JakartaJsonProvider(true)
(включить изменяемые массивы и объекты Json)JsonProvider jsonProvider = new JakartaJsonProvider()
(по умолчанию, строгое соответствие API JSON-P)Все операции поиска и чтения с помощью JsonPath поддерживаются независимо от режима инициализации. Режим по умолчанию также требует меньше памяти и более эффективен.
В JsonPath 2.1.0 был представлен новый Cache SPI. Это позволяет потребителям API настраивать кэширование путей таким образом, который соответствует их потребностям. Кэш необходимо настроить до первого доступа к нему, иначе будет выдано исключение JsonPathException. JsonPath поставляется с двумя реализациями кэша.
com.jayway.jsonpath.spi.cache.LRUCache
(по умолчанию, потокобезопасный)com.jayway.jsonpath.spi.cache.NOOPCache
(без кеша)Если вы хотите реализовать свой собственный кеш, API прост.
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 );
}
});