Современный 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 процессора?

    • 100,0%Да, попробую использовать1
    • 0,0%Нет, буду зубрить jq0

    Средняя зарплата в IT

    120 000 ₽/мес.
    — средняя зарплата во всех IT-специализациях по данным из 9 851 анкеты, за 1-ое пол. 2021 года. Проверьте «в рынке» ли ваша зарплата или нет! Проверить свою зарплату
    Реклама
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее

    Комментарии 0

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

    Самое читаемое