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

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Вместо тысячи слов... Хотели бы вы вместо jq, парсить 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 с его чудовищным синтаксисом. Так как меня это не устраивало, решил сделать современный 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:

// уже знакомые по json методы, возвращают ноду с сабнодами:
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 и можем вводить любые запросы на ваш вкус.

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

Глядя на этот синтаксис можно было подумать что внутри творится ад ЯП-строения, но на самом деле все гораздо проще. Запросы являются валидным подмножеством 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

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

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Интересный ли вариант JSON процессора?

  • 75,0%Да, попробую использовать3
  • 25,0%Нет, буду зубрить jq1
Источник: https://habr.com/ru/post/565576/


Интересные статьи

Интересные статьи

Статья о том, как упорядочить найм1. Информируем о вакансии2. Ведём до найма3. Автоматизируем скучное4. Оформляем и выводим на работу5. Отчитываемся по итогам6. Помогаем с адаптацией...
Калифорнийская компания Ampere представила первый в отрасли 80-ядерный серверный ARM-процессор на 64-битной архитектуре Ampere Altra. Уже несколько лет специалисты прогнозируют, что платфо...
Речь пойдёт о способе защиты програмного обеспечения, который реализован в самом процессоре. В качества экспериментов я выбрал мультимедиа приставку Comigo Quattro. Цель — запустить своё ядро лин...
На данный момент LLVM стала уже очень популярной системой, которую многие активно используют для создания различных компиляторов, анализаторов и т.п. Уже написано большое количество полезных мате...
Сегодня мы поговорим о перспективах становления Битрикс-разработчика и об этапах этого пути. Статья не претендует на абсолютную истину, но даёт жизненные ориентиры.