Использование 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


Также связано 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

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)но он немного более специфичен, так как определяет уровень глубины для извлечения желаемых данных.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.