Всех приветствую, давненько не писал, но тут один коллега замотивировал, сказав, что моя статья по JsonPath — лучшее что есть по этой теме в рунете. Решил немного расширить тему и добавить полезных вещей.
Итак, это продолжение вот этой статьи, если вы ее не читали, то ознакомьтесь о чем пойдет речь. В примерах ниже будет использоваться тот же json и та же библиотека JsonPath из поставки RestAssured.
Сразу дам совет, который и определит зачем я рассказываю про все эти функции груви: нужно постараться сделать максимум парсинга, приведений, изменений, сортировки на уровне запроса, чтобы получив результат уже работать с ним, далее не преобразовывая. Я к тому, что часто вижу (не только при работе c JsonPath), что стараются наоборот написать минимальный запрос, получить поскорее коллекцию, например мапу в Java и потом начинают уже с ней работать, прикручивать стримы, сортировать и так далее. На мой взгляд, если уж мы все равно пишем запрос для груви -есть смысл сразу все наши проблемы решить на его уровне и для джавы получить просто результат, который использовать в ассертах/проверках.
1. Unique -удаление дубликатов с условием
Мы рассмотрели функцию уникальности в прошлый раз, но на самом деле она может применяться гораздо шире, так как ей можно задать параметры для определения уникальности в том же стиле, что и любые условие unique {условие}
class.unique {it.name} — вернет нам два объекта, а именно Иванова Ивана и Петрова Петю. Сидорова мы не получим, потому что имя Иван уже есть в наборе (это имя Иванова), поэтому все остальные Иваны игнорируются.
Обращаю внимание, что при наличии нескольких объектов с полем, по которому проверяем уникальность в результат попадет ТОЛЬКО ПЕРВЫЙ, а не последний, как мы привыкли при работе с мапой в Java!
class.unique {it.name.size()} — вернет только одного (и что важно-первого) Иванова Ивана, так как уникальность теперь проверяется по длине имени, она у всех одинакова и получаем только первый объект.
2. Sort -сортировка по различным признакам
class.sort {it.surname} — вернет нам объекты в таком порядке Иванов, Петров, Сидоров, потому что сортировка их фамилий идет по алфавиту.
Помним, что сортировка строк и чисел идет по разному, вспоминаем пример из прошлой статьи!
class.sort {it.marks.sum()/it.marks.size()}.reverse() — вернет нам наших учеников, отсортированных по среднему баллу, то есть начиная с Петрова у которого средний балл 4.5 и заканчивая двоечником Сидоровым, у которого средний балл 2.8
3. Проверки, возвращающие boolean: условия, every(), any()
Иногда нам из всего жсона получить не какие-то данные, а только конкретный ответ. Скажем получить только учеников, получавших пятерки или узнать -есть ли вообще ученики с пятерками. То есть нам нужны не сами данные, а ответ true/false на основе этих данных.
class.findAll {it.marks.contains(5)} — вернет объекты Иванова и Петрова, так как у них обоих есть пятерка в оценках
class.every {it.marks.contains(5)} — вернет false, так как every проверяет что ВСЕ объекты удовлетворяют условию, а у нас к сожалению Сидоров не получал пятерки ни разу.
class.any {it.marks.contains(5)} — вернет true, так как any проверяет, что ХОТЬ ОДИН из объектов удовлетворяет условию, а у нас есть те, кто получал 5, например Иванов.
4. Изменение и добавление для каждого элемента each {}
class.each {it.marks = it.marks.sum()/it.marks.size()} — превратит список оценок каждого ученика в средний балл, то есть на выходе будет список, типа [{address={street=Lenin, house=12}, surname=Ivanov, name=Ivan, marks=3.6, age=13}, {address={street=Marx, house=7}, surname=Petrov, name=Petr, marks=4.5, age=14}, {address={street=Pobedy, house=9}, surname=Sidorov, name=Ivan, marks=2.8, some.value=42, age=14}]
class.each {it.average = it.marks.sum()/it.marks.size()}.sort {it.average} — создаст для каждого ученика новое поле, содержащее средний балл и отсортирует учеников по этому новому полю
То есть можно как менять имеющиеся поля, преобразовывать их, так и добавлять новые
5. Map и работа с ними
Напомню что json объект груви считает LinkedHashMap, можно применять методы мапы для работы с объектами
class[0].keySet() — вернет [address, surname, name, marks, age] то есть все ключи нашего объекта Иванова
class[0].values() — вернет наоборот список всех значений нашего Иванова [{street=Lenin, house=12}, Ivanov, Ivan, [2, 3, 4, 4, 5], 13]
class.collect {[(it.surname):it.marks]} — вернет нам список мап, содержащих только фамилию как ключ и оценки, как значение [{Ivanov=[2, 3, 4, 4, 5]}, {Petrov=[5, 5, 3, 5]}, {Sidorov=[3, 4, 2, 2, 3]}]
[(it.surname):it.marks] — это синтаксис создания мапы в груви, то ест ключ и значение через двоеточие, скобки в данном случае обязательны вокруг ключа, они заменяют кавычки
Гораздо чаще, нам надо получить не список мап, а одну общую мапу из всего json, например возраст учеников по их фамилиям. Для этого надо немного изменить сам java код запроса
Map<String,Integer> ages = JsonPath.from(json).param("map", new HashMap<String,Integer>()). getMap("class.each {map[it.surname]=it.age.toInteger()}; return map"); System.out.println(ages);
в первой строке мы создаем переменную ages, сразу решаем что строка будут мапиться на инт (возраст). После знака равно мы парсим наш жсон и создаем «внешнюю» мапу, которую передаем внутрь нашего запроса с именем map.
Во второй строке, мы говорим что будем парсить именно мапу и указываем запрос для нее
class.each {map[it.surname]=it.age.toInteger()}; return map — для каждого элемента (each) на уровне class добавляем в нашу внешнюю мапу ключ равный фамилии и значение равное возрасту, приведенному к инту. После точки с запятой, которая отделяет операции, возвращаем из запроса нашу внешнюю мапу, уже заполненную значениями.
В последней строке выводим нашу мапу в консоль.
Важно отметить, что внутри запроса выполняется две операции — сначала заполняем мапу, затем ее возвращаем, их нужно отделять точкой с запятой, как и в Java.
Запрос вернет нам одну результирующую мапу {Petrov=14, Ivanov=13, Sidorov=14}
И несколько мелочей!
Напоминаю, что collect всегда вернет список, однако его значения можно и преобразовывать в нужный нам формат
class.collect {it.name + ‘ ‘ + it.surname} — вернет нам список строк [Ivan Ivanov, Petr Petrov, Ivan Sidorov], обратите внимание как мы конкатенировали имя и фамилию с пробелом внутри условия (между апострофами пробел!).
first() и last() — это более читаемая замена индексам [0] и [-1], например
class.first().marks.last() — вернет число 5, то есть последнюю оценку первого ученика
На этом пожалуй все, оперируя этими функциями и не забывая читать документацию, можно выдернуть, преобразовать и получить нужные нам данные из json сразу в нужном формате, чтобы свести дальнейшие манипуляции с ними к минимуму. Всем удачи!