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.


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


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"

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")'

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.


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.


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

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.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.