Использование jq для извлечения значений и форматирования в CSV

58

У меня есть файл JSON ниже:

{
"data": [
    {
        "displayName": "First Name",
        "rank": 1,
        "value": "VALUE"
    },
    {
        "displayName": "Last Name",
        "rank": 2,
        "value": "VALUE"
    },
    {
        "displayName": "Position",
        "rank": 3,
        "value": "VALUE"
    },
    {
        "displayName": "Company Name",
        "rank": 4,
        "value": "VALUE"
    },
    {
        "displayName": "Country",
        "rank": 5,
        "value": "VALUE"
    },
]
}

Я хотел бы иметь файл CSV в этом формате:

First Name, Last Name, Position, Company Name, Country
VALUE, VALUE, VALUE, VALUE, VALUE, VALUE

Это возможно только при использовании jq? У меня нет никаких навыков программирования.

Керим
источник
1
Я дал ответ ниже, но сейчас я внимательно изучаю ваш вопрос, и я не могу не задаться вопросом - откуда берется 6-е ЗНАЧЕНИЕ ?
mikeserv
1
Связано с SO: stackoverflow.com/questions/25558456/…
Антон Тарасенко
Также связано stackoverflow.com/q/32960857/168034
phunehehe

Ответы:

50

У jq есть фильтр @csv для преобразования массива в строку CSV. Этот фильтр учитывает большинство сложностей, связанных с форматом CSV, начиная с запятых, встроенных в поля. (jq 1.5 имеет аналогичный фильтр @tsv для генерации файлов с разделенными табуляцией значениями.)

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

Например, если «Название компании» было «Смит, Смит и Смит», и если другие значения были такими, как показано ниже, то при вызове jq с параметром «-r» будет получен действительный CSV:

$ jq -r '.data | map(.displayName), map(.value) | @csv' so.json2csv.json
"First Name","Last Name","Position","Company Name","Country"
"John (""Johnnie"")","Doe","Director, Planning and Posterity","Smith, Smith and Smith","Transylvania"
вершина горы
источник
3
Я был в состоянии 'JQ некоторые вещи | карта (.) | @csv ', очень удобно! Спасибо
flickerfly
3
В вашем примере все отображаемые имена будут помещены в первую строку, а все значения - во вторую, вместо одной строки на запись.
Брайан Гордон
33

Я предпочитаю делать каждую запись строкой в ​​моем CSV.

jq '.data | map([.displayName, .rank, .value] | join(", ")) | join("\n")'
Сайлас Пол
источник
2
Что если .value - это число? Я получаю сообщение об ошибке «строка и число не могут быть добавлены»
Cos
2
@ Что-то вроде .value|tostringтого, что было .valueв приведенном выше примере
matheeeny
4
@Cos, я нашел скобки обязательны. (.value|tostring)
ciscogambo
Кроме того, используйте, jq -rчтобы раздеть цитаты
Глина
30

Учитывая только этот файл, вы можете сделать что-то вроде:

<testfile jq -r '.data | map(.displayName), map(.value) | join(", ")'

.Оператор выбирает поле из объекта / хэша. Итак, начнем с того .data, что возвращает массив с данными в нем. Затем мы дважды отображаем массив, сначала выбираем displayName, затем выбираем значение, давая нам два массива только со значениями этих ключей. Для каждого массива мы соединяем элементы с помощью «,», образуя две строки. -rАргумент говорит jqне процитировать получившиеся строки.

Если ваш фактический файл длиннее (то есть содержит записи для более чем одного человека), вам, вероятно, понадобится что-то более сложное.

Стивен Д
источник
Это не работает для меня. В связанной теме ответ stackoverflow.com/questions/32960857/… и работает, и очень хорошо объяснен!
Эрве
10

Мне было jqтрудно обернуть голову. Вот немного Ruby:

ruby -rjson -rcsv -e '
  data = JSON.parse(File.read "file.json")
  data["data"].collect {|item| [item["displayName"], item["value"]]}
              .transpose
              .each {|row| puts row.to_csv}
'
First Name,Last Name,Position,Company Name,Country
VALUE,VALUE,VALUE,VALUE,VALUE

Рубиновый JSON-анализатор перебрал запятую перед закрывающей скобкой.

Гленн Джекман
источник
2

Поскольку вы отметили это pythonи предполагаете, что имя jsonфайлаx.json

import os, json
with open('x.json') as f:
    x  = json.load(f)
    print '{}{}{}'.format(', '.join(y['displayName'] for y in x['data']), os.linesep,
             ', '.join(y['value'] for y in x['data']))
First Name, Last Name, Position, Company Name, Country
VALUE, VALUE, VALUE, VALUE, VALUE
Iruvar
источник
1

Хотя мне пришлось удалить последнюю запятую в вашем примере ввода, чтобы она заработала, потому что jqжаловалась на ожидание другого элемента массива, это:

INPUT | jq -r '[.[][].displayName], [.[][].value]| join(", ")'

...подловил...

First Name, Last Name, Position, Company Name, Country
VALUE, VALUE, VALUE, VALUE, VALUE

Как это работает в двух словах:

  1. Я прошел к третьему уровню объектов данных, используя пустую []форму поля индекса и .dotобозначения.
  2. Однажды, достаточно глубоко, я указал нужные поля данных по имени вроде .[][].displayName.
  3. Я заверил, что мои нужные поля были связаны с собой, возвращая их как отдельные объекты массива, такие как [.[][].displayName], [.[][].value]
  4. А затем передал эти объекты в join(", ")функцию для объединения в виде отдельных объектов.

По правде говоря, [.field]это просто другой способ, map(.field)но он немного более специфичен, так как определяет уровень глубины для извлечения желаемых данных.

mikeserv
источник