Używanie jq do wyodrębniania wartości i formatowania w CSV

58

Mam poniższy plik 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"
    },
]
}

Chciałbym mieć plik CSV w tym formacie:

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

Czy jest to możliwe tylko przy użyciu jq? Nie mam żadnych umiejętności programistycznych.

Kerim
źródło
1
Podałem odpowiedź poniżej, ale teraz przyglądam się bliżej Twojemu pytaniu i nie mogę przestać się zastanawiać - skąd ma pochodzić 6. WARTOŚĆ ?
mikeserv
1
Powiązane z SO: stackoverflow.com/questions/25558456/…
Anton Tarasenko
Również powiązany stackoverflow.com/q/32960857/168034
phunehehe

Odpowiedzi:

50

jq ma filtr @csv do konwersji tablicy na ciąg CSV. Filtr ten uwzględnia większość zawiłości związanych z formatem CSV, zaczynając od przecinków osadzonych w polach. (jq 1.5 ma podobny filtr @tsv do generowania plików wartości rozdzielanych tabulatorami).

Oczywiście, jeśli wszystkie nagłówki i wartości są wolne od przecinków i podwójnych cudzysłowów, może nie być potrzeby używania filtra @csv. W przeciwnym razie prawdopodobnie lepiej byłoby go użyć.

Na przykład, jeśli „Company Name” to „Smith, Smith and Smith”, a inne wartości są takie, jak pokazano poniżej, wywołanie jq z opcją „-r” wygeneruje prawidłowy 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"
szczyt
źródło
3
Byłem w stanie 'jq somestuff | mapa (.) | @csv ', bardzo przydatne! Dzięki
flickerfly,
3
Twój przykład polega na umieszczeniu wszystkich wyświetlanych nazw w pierwszym wierszu i wszystkich wartości w drugim wierszu, zamiast posiadania jednego wiersza na rekord.
Brian Gordon,
33

Wolę, aby każdy rekord był wierszem w moim pliku CSV.

jq '.data | map([.displayName, .rank, .value] | join(", ")) | join("\n")'
Silas Paul
źródło
2
Co jeśli. Wartość jest liczbą? Pojawia się komunikat „Nie można dodać ciągu i numeru”
Cos
2
@Cos coś takiego .value|tostringzamiast .valuew powyższym przykładzie
matheeeny
4
@Cos, znalazłem nawiasy są wymagane. (.value|tostring)
ciscogambo
Użyj też, jq -raby rozebrać cytaty
Clay
30

Biorąc pod uwagę tylko ten plik, możesz zrobić coś takiego:

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

.Operator wybiera się pola z obiektu / mieszania. Dlatego zaczynamy od .data, który zwraca tablicę z zawartymi w niej danymi. Następnie dwukrotnie odwzorowujemy tablicę, najpierw wybierając displayName, a następnie wartość, dając nam dwie tablice z wartościami tylko tych kluczy. Dla każdej tablicy łączymy elementy za pomocą „,” tworząc dwie linie. -rArgumentem mówi jqnie zacytować wynikające sznurki.

Jeśli rzeczywisty plik jest dłuższy (tzn. Zawiera wpisy dla więcej niż jednej osoby), prawdopodobnie będziesz potrzebować czegoś bardziej skomplikowanego.

Steven D.
źródło
To nie działa dla mnie. W pokrewnym temacie odpowiedź stackoverflow.com/questions/32960857/… działa i bardzo dobrze wyjaśniono!
herve
10

Trudno mi jqbyło owinąć głowę. Oto niektóre 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

Rubinowy parser JSON wytoczył kres wokół przecinka przed zamykającym nawias.

Glenn Jackman
źródło
2

Ponieważ oznaczono to tagiem pythoni przyjęto nazwę jsonplikux.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
źródło
1

Chociaż musiałem usunąć ostatni przecinek z przykładowego wejścia, aby działało, ponieważ jqnarzekałem na oczekiwanie innego elementu tablicy, to:

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

...masz mnie...

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

Jak to działa w skrócie:

  1. Przeszedłem na trzeci poziom obiektów danych, używając pustej []formy pola indeksu i .dotzapisu.
  2. Kiedyś wystarczająco głęboko, określiłem pola danych, które chciałem po nazwie jak .[][].displayName.
  3. Zapewniłem, że moje żądane pola zostały skojarzone samodzielnie, zwracając je jako osobne obiekty tablicowe, takie jak [.[][].displayName], [.[][].value]
  4. A następnie potokował te obiekty do join(", ")funkcji, aby połączyć je jako oddzielne jednostki.

W rzeczywistości robienie [.field]jest tylko innym sposobem, map(.field)ale jest to nieco bardziej szczegółowe, ponieważ określa poziom głębokości pobierania pożądanych danych.

mikeserv
źródło