Современный JSON процессор

Хотели бы вы парсить ndjson’ы в терминале вот таким образом?

where { bool("muted") } max { long("size") } top 3  {"id":4880123,"size":245,"muted":true} {"id":2392636,"size":107,"muted":true} {"id":15843320,"size":59,"muted":true}

В работе мы нередко используем выгрузки в формате ndjson. Однако их анализ затруднен из-за того что стандартным инструментом для этого является jq с его чудовищным синтаксисом порождающим такое:

cat file.ndjson | jq '.tickets | map(select(.assigned_displayname=="MyNameOnApi"))' | jq '.[] | "\(.id) \(.title) \t \(.description)\n"' | sed #some after jq prettifying 

Так как мне это не нравится, решил сделать современный json-процессор с удобочитаемым DSL. Что уже умеет этот инструмент?

Прежде всего, в наличии есть пять sql-подобных управляющих конструкций для обработки:

where { /* условие фильтрации */ } order { /* выражение для задания порядка */ } min   { /* выражение для поиска минимальных значений */ } max   { /* выражение для поиска максимальных значений  */ } top   ( /* число, лимит для выборки */ )

Все они могут быть использованы как по одиночке, так и скомбинированы друг с другом в любом порядке и с любым количеством повторов. Внутри используется обычный Kotlin, поэтому в выражениях вы также ничем не ограничены. Управляющие конструкции скомбинированные вкупе с выражениями образуют запрос который немедленно вернет вам набор json’ок. Сами запросы также можно комбинировать друг с другом, тогда они будут обработаны за один проход и вы получите несколько выборок в результате.

Примеры комбинации выражений

Этот набор запросов вернет нам 5 разных выборок json. Постарался подчеркнуть читаемость:

max { long("size") } top 3 where { bool("active") },  top(5) where { !bool("active") } min { int("some") },  top(5) min { time("first") to time("last") }, // duration  where { get("arr") int (0) > 5 },  where { !get("broken") } top 3 min { get(4) get("nested") bool("flag") }

Если вы присмотрелись к этому примеру внимательнее, то заметили что в самих выражениях тоже используется лаконичный синтаксис доступа к json полям. Это стало возможно благодаря определенным хелперам выражений, которые являются ничем иным как расширениями для jackson:

// уже знакомые по jackson методы, возвращают ноду: get(name: String) get(idx: String)  // о назачении дополнительных расширений комментариев не требуется: bool(name: String) bool(idx: String) int(name: String) int(idx: String) double(name: String) double(idx: String) string(name: String) string(idx: String) time(name: String) time(idx: String)

Где взять, как начать пользоваться?

Из зависимостей вам потребуется лишь Docker и интернет. Прежде всего запустите контейтер с утилитой в вашей директории, например:

docker run -v desired/dir:/opt -it demidko/analyze

Теперь вам доступен шелл контейнера с командой analyze которой можно открывать ndjson файлы:

analyze file.ndjson

Вот и все! Мы попали в шелл для json и можем вводить любые запросы на ваш вкус.

Пара слов о внутренней реализации

Глядя на этот синтаксис можно было подумать что внутри творится ад dsl-строения, но на самом деле все гораздо проще. Запросы являются валидным подмножеством Kotlin и используют определенные прямо в исходном коде Kotlin функции. Запускается эта радость через известный jsr223 на котором подробно останавливаться нет смысла.
А вот про реализацию самих запросов стоит рассказать, остановлюсь на примере из кода:

typealias Query = (MutableList<JsonNode>, JsonNode) -> Unit  class Action(val action: MutableList<JsonNode>.(JsonNode) -> Unit) :   Query by { list, el ->     list.action(el)   }    infix fun Action.top(limit: Int) = Action {   action(it)   while (size > limit) {     removeLast()   } }  fun order(comparator: Comparator<JsonNode>) = Action {   add(it)   sortWith(comparator) }  infix fun Action.where(filter: JsonNode.() -> Boolean) = Action {   if (it.filter()) {     action(it)   } }

Возможно вы удивитесь, но эти несколько функций и образуют ядро синтаксиса благодаря инфиксной нотации, а все остальное по сути синтаксические дополнения.

Исходный код лежит на GitHub: https://github.com/demidko/analyze

Всем удачного дня! Буду рад услышать критику и предложения.

ссылка на оригинал статьи https://habr.com/ru/post/565576/

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *