Извлечение двух (или более) связанных значений из массива объектов JSON

Вопрос или проблема

Рассмотрим надуманный пример, используя JSON-объект, такой как этот, где я хочу извлечь связанные поля id, firstname и lastname для каждого из многих объектов массива в переменные shell для дальнейшей обработки (не-JSON).

{
  "customers": [
    {
      "id": 1234,
      "firstname": "John",
      "lastname": "Smith",
      "other": "fields",
      "are": "present",
      "here": "etc."
    },
    {
      "id": 2468,
      "firstname": "Janet",
      "lastname": "Green",
      "other": "values",
      "are": "probably",
      "here": "maybe"
    }
  ]
}

Для простых данных я могу использовать это,

jq -r '.customers[] | (.id + " " + .firstname + " " + .lastname)' <data.json |
    while IFS=' ' read id firstname lastname
    do
        # Дополнительная обработка, но опущена для примера
        printf '%s -- %s -- %s\n' "$id" "$firstname" "$lastname"
    done

Вывод

1234 -- John -- Smith
2468 -- Janet -- Green

но, конечно, это потерпит неудачу с двойными именами firstname, такими как Anne Marie. Изменение разделителя на другой символ, такой как #, кажется скорее уловкой, чем решением, но может быть приемлемым.

Для более сложных ситуаций я могу выбрать список значений id, а затем обменять скорость на точность, вернувшись, чтобы извлечь соответствующие элементы firstname и lastname. Что-то вроде этого:

jq -r '.customers[].id' <data.json |
    while IFS= read id
    do
        block=$(jq -r --arg id "$id" '.customers[] | select(.id == $id)' <data.json); 

        firstname=$(jq -r '.firstname' <<<"$block")
        lastname=$(jq -r '.lastname' <<<"$block")

        # Дополнительная обработка, но опущена для примера
        printf '%s -- %s -- %s\n' "$id" "$firstname" "$lastname"
    done

Вывод

1234 -- John -- Smith
2468 -- Janet -- Green

Однако ни один из этих методов не является одновременно правильным и эффективным. Хотя я не собираюсь запускать настоящий код с высокой частотой, мне бы хотелось понять, существует ли более подходящий способ безопасного и эффективного получения множества элементов данных из структуры JSON-объекта в переменные shell?

Я действительно не вижу проблемы в выборе другого разделителя, например, табуляции. Это довольно традиционный выбор разделителя в Unix-приложениях обработки данных (по умолчанию для paste и cut, например). А jq имеет оператор вывода @tsv, который вы могли бы использовать:

jq -r '.customers[] | [.id, .firstname, .lastname] | @tsv' file |
while IFS=$'\t' read id first last
do
    printf '%s - %s - %s\n' "$id" "$first" "$last"
done

Я бы использовал фильтр @tsv:

jq -r '.customers[] | [.id, .firstname, .lastname] | @tsv' < data.json |
while IFS=$'\t' read -r id firstname lastname
do
    printf '%b -- %b -- %b\n' "$id" "$firstname" "$lastname"
done

Будьте осторожны с пустыми полями, поскольку read может “сломаться”; вам следует убедиться, что всегда выводится как минимум один символ на поле.

Примечание: %b служит для декодирования escape-последовательностей @tsv

Ответ или решение

Извлечение связанных значений из массива JSON-объектов для последующей обработки в оболочке — частая задача при работе с данными. Контекст задачи таков: у нас есть JSON-объект, в котором содержатся данные о клиентах, и мы хотим извлечь связанные поля id, firstname и lastname из каждого объекта массива в отдельные переменные оболочки. Однако проблема заключается в том, чтобы делать это эффективно и корректно даже в случае, если имя клиента состоит из нескольких слов. Давайте рассмотрим этот вопрос более подробно.

Теория

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

Обычно, когда данные просты и структурированы, стандартные подходы заключаются в линейном извлечении данных с использованием несложных фильтров. Однако когда данные становятся более сложными, например, включают в себя пробелы или другие специальные символы, необходимо применять более надежные методы для извлечения данных, чтобы избежать ошибок. Основной вызов в данной задаче заключается в том, чтобы извлекать несколько полей из JSON-объекта и корректно преобразовывать их в переменные оболочки, не теряя данные из-за некорректного разделения их на пробелы.

Пример

Рассматриваемый JSON выглядит следующим образом:

{
  "customers": [
    {
      "id": 1234,
      "firstname": "John",
      "lastname": "Smith",
      "other": "fields",
      "are": "present",
      "here": "etc."
    },
    {
      "id": 2468,
      "firstname": "Janet",
      "lastname": "Green",
      "other": "values",
      "are": "probably",
      "here": "maybe"
    }
  ]
}

Для извлечения данных, как было предложено, мы можем воспользоваться следующим скриптом:

jq -r '.customers[] | [.id, .firstname, .lastname] | @tsv' < data.json |
while IFS=$'\t' read -r id firstname lastname
do
    printf '%b -- %b -- %b\n' "$id" "$firstname" "$lastname"
done

Вышеописанный скрипт использует @tsv фильтр jq для вывода данных в формате, разделенном табуляцией. Это значит, что каждое значение будет разделено символом табуляции, что позволяет корректно различать значения, даже если они содержат пробелы, без необходимости сложной логики обработки. Команда read используется здесь с модификатором IFS=$'\t' для правильного разбора строк на основе символов табуляции.

Применение

  1. Корректность и надежность: Применение @tsv оператора помогает избежать ошибок, связанных с пробелами или другими разделителями внутри полей данных. Это более надежное решение по сравнению с использованием произвольных символов, особенно когда существует возможность встречи двусоставных имен.

  2. Эффективность: Это решение позволяет сразу извлекать все необходимые данные за один проход, не вызывая jq многократно, что особенно важно при работе с большими файлами данных. Значительное сокращение числа операций над данными может существенно повлиять на скорость выполнения скрипта в процессе массовой обработки.

  3. Совместимость и стандарты: Использование разделителя табуляции соответствует стандартным практикам Unix-систем, где процедуры обработки данных с разделителями являются рутиной. Это делает решение совместимым с другими инструментами обработки данных, такими как cut, awk, и sed, которые также работают с табуляцией.

  4. Простота понимания и поддержки: Благодаря встроенным возможностям jq и стандартным возможностям bash скрипт остается компактным и легко понимаемым, что упрощает его поддержку и обновление при изменении структуры данных.

Заключение

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

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

Капча загружается...