Удалить совпадающие/не совпадающие элементы вложенного массива с помощью jq

Мне нужно разделить результаты истории анализа сонарка на отдельные файлы. Предполагая начальный ввод ниже,

    {
  "paging": {
    "pageIndex": 1,
    "pageSize": 100,
    "total": 3
  },
  "measures": [
    {
      "metric": "coverage",
      "history": [
        {
          "date": "2018-11-18T12:37:08+0000",
          "value": "100.0"
        },
        {
          "date": "2018-11-21T12:22:39+0000",
          "value": "100.0"
        },
        {
          "date": "2018-11-21T13:09:02+0000",
          "value": "100.0"
        }
      ]
    },
    {
      "metric": "bugs",
      "history": [
        {
          "date": "2018-11-18T12:37:08+0000",
          "value": "0"
        },
        {
          "date": "2018-11-21T12:22:39+0000",
          "value": "0"
        },
        {
          "date": "2018-11-21T13:09:02+0000",
          "value": "0"
        }
      ]
    },
    {
      "metric": "vulnerabilities",
      "history": [
        {
          "date": "2018-11-18T12:37:08+0000",
          "value": "0"
        },
        {
          "date": "2018-11-21T12:22:39+0000",
          "value": "0"
        },
        {
          "date": "2018-11-21T13:09:02+0000",
          "value": "0"
        }
      ]
    }
  ]
}

Как использовать jq для очистки результатов, чтобы он сохранял только записи массива истории для каждого элемента? Желаемый результат примерно такой (output-20181118123808.json для анализа, выполненного на «2018-11-18T12:37:08+0000»):

{
  "paging": {
    "pageIndex": 1,
    "pageSize": 100,
    "total": 3
  },
  "measures": [
    {
      "metric": "coverage",
      "history": [
        {
          "date": "2018-11-18T12:37:08+0000",
          "value": "100.0"
        }
      ]
    },
    {
      "metric": "bugs",
      "history": [
        {
          "date": "2018-11-18T12:37:08+0000",
          "value": "0"
        }
      ]
    },
    {
      "metric": "vulnerabilities",
      "history": [
        {
          "date": "2018-11-18T12:37:08+0000",
          "value": "0"
        }
      ]
    }
  ]
}

Я не понимаю, как работать только с подэлементами, оставляя родительскую структуру нетронутой. Именование файла JSON будет обрабатываться извне утилитой jq. Предоставленные образцы данных будут разделены на 3 файла. Некоторые другие входные данные могут иметь переменное количество записей, некоторые могут быть до 10000. Спасибо.


person ramfree17    schedule 27.11.2018    source источник
comment
Часть Q искажена (как я могу удалить). Кроме того, учитывая показанный ввод, сколько файлов вы ожидаете? По одному на каждую дату? Как должны называться файлы?   -  person peak    schedule 27.11.2018
comment
Именование выходных файлов изначально будет обрабатываться отдельно, но если это можно сделать из jq, то это будет большим плюсом! :D   -  person ramfree17    schedule 27.11.2018


Ответы (2)


Вот решение, которое использует awk для записи отдельных файлов. Решение предполагает, что даты для каждой меры одинаковы и расположены в одном и том же порядке, но не накладывает ограничения на количество различных дат или количество различных мер.

jq -c 'range(0; .measures[0].history|length) as $i
  | (.measures[0].history[$i].date|gsub("[^0-9]";"")),  # basis of filename
    reduce range(0; .measures|length) as $j (.;
      .measures[$j].history |= [.[$i]])' input.json |
awk -F\\t 'fn {print >> fn; fn="";next}{fn="output-" $1 ".json"}'

Комментарии

Выбор awk здесь просто для удобства.

Недостатком этого подхода является то, что если каждый файл должен быть аккуратно отформатирован, для каждого файла потребуется дополнительный запуск симпатичного принтера (например, jq). Таким образом, если требуется, чтобы вывод в каждом файле был аккуратным, можно было бы запустить jq один раз для каждой даты, что устраняет необходимость в шаге постобработки (awk).

Если даты мер не совпадают, то можно использовать тот же подход, что и выше, но, конечно, сбор дат и соответствующих мер должен быть выполнен по-другому.

Выход

Первые две строки, полученные при вызове jq выше, следующие:

"201811181237080000"
{"paging":{"pageIndex":1,"pageSize":100,"total":3},"measures":[{"metric":"coverage","history":[{"date":"2018-11-18T12:37:08+0000","value":"100.0"}]},{"metric":"bugs","history":[{"date":"2018-11-18T12:37:08+0000","value":"0"}]},{"metric":"vulnerabilities","history":[{"date":"2018-11-18T12:37:08+0000","value":"0"}]}]}
person peak    schedule 27.11.2018
comment
есть ли вариант, в котором фильтрация основана на значении даты, а не на позиции? Не гарантируется, что порядок будет одинаковым или количество элементов в каждой метрике будет одинаковым (т. е. в некоторых датах могут отсутствовать ошибки, в некоторых может быть дополнительная метрика, например, сложность). - person ramfree17; 30.11.2018
comment
Да, и тот же подход можно использовать, но это потребует некоторой работы. Поскольку SO не является бесплатной службой программирования, возможно, пришло время изучить jq :-) - person peak; 30.11.2018
comment
Я пытаюсь изучить это, и я извиняюсь, если я столкнусь с тем, что прошу полную программу. Мне нужен фрагмент кода, который фильтрует (или удаляет) несовпадающие записи встроенного массива. Попробую разобраться, исходя из того, что вы предоставили. На данный момент я пошел с более дорогостоящим запросом SonarQube для каждой даты, которая выполняет фильтрацию. Спасибо. - person ramfree17; 02.12.2018

В комментариях появилось следующее дополнение к исходному вопросу:

есть ли вариант, в котором фильтрация основана на значении даты, а не на позиции? Не гарантируется, что порядок будет одинаковым или количество элементов в каждой метрике будет одинаковым (т. е. в некоторых датах могут отсутствовать «ошибки», в некоторых может быть дополнительная метрика, например «сложность»).

Следующее создаст поток объектов JSON, по одному на дату. Этот поток может быть аннотирован датой в соответствии с моим предыдущим ответом, который показывает, как использовать эти аннотации для создания различных файлов. Для простоты понимания воспользуемся двумя вспомогательными функциями:

def dates:
  INDEX(.measures[].history[].date; .)
  | keys;

def gather($date): map(select(.date==$date));

dates[] as $date
| .measures |= map( .history |= gather($date) )

ИНДЕКС/2

Если у вашего jq нет INDEX/2, сейчас самое время обновиться, но если это невозможно, вот его определение:

def INDEX(stream; idx_expr):
  reduce stream as $row ({};
    .[$row|idx_expr|
      if type != "string" then tojson
      else .
      end] |= $row);
person peak    schedule 02.12.2018
comment
спасибо пик. предназначены ли они для помещения в файл и чтения jq? Я не знал, что jq позволяет интерпретировать инструкции из файла, поэтому для меня это новый способ использования jq. - person ramfree17; 03.12.2018
comment
Ага, пора поближе взглянуть на руководство по jq :-) stedolan.github .io/jq/manual/v1.6 - person peak; 03.12.2018
comment
Это объясняет, почему я не могу найти вариант. Версия Ubuntu 18.04 по-прежнему 1.5, и мне нужно поддерживать совместимость с другими людьми, которые будут использовать сценарии, которые я создам. Спасибо за внимание! - person ramfree17; 03.12.2018
comment
Я не уверен, какой вариант вы имеете в виду, но я добавил определение INDEX/2 в ответ. - person peak; 03.12.2018
comment
Я имел в виду вариант чтения файла. Это недоступно в jq 1.5. Модернизация подходит для моей машины, но мне нужно поддерживать совместимость с другими машинами в системе. Спасибо за все пункты, чтобы прочитать и понять о. :) - person ramfree17; 03.12.2018
comment
Параметр -f был доступен (и задокументирован) в течение многих лет, так что может показаться, что вы ошибаетесь. - person peak; 03.12.2018
comment
да, я. Я смотрел на вывод jq --help, когда у меня должен был быть RTFM. :D - person ramfree17; 03.12.2018