Jq kullanarak rastgele basit JSON'u CSV'ye nasıl dönüştürebilirim?


106

Jq kullanarak , bir dizi sığ nesneyi kodlayan keyfi JSON nasıl CSV'ye dönüştürülebilir?

Bu sitede, alanları sabit kodlayan belirli veri modellerini kapsayan birçok Soru ve Cevap vardır, ancak bu soruya verilen yanıtlar, skaler özelliklere sahip bir nesne dizisi olduğu (derin / karmaşık / alt nesneler, çünkü bunları düzleştirmek başka bir sorudur). Sonuç, alan adlarını veren bir başlık satırı içermelidir. İlk nesnenin alan sırasını koruyan yanıtlar tercih edilecektir, ancak bu bir gereklilik değildir. Sonuçlar, tüm hücreleri çift tırnak içine alabilir veya yalnızca alıntı yapılmasını gerektirenleri (örneğin, 'a, b') içine alabilir.

Örnekler

  1. Giriş:

    [
        {"code": "NSW", "name": "New South Wales", "level":"state", "country": "AU"},
        {"code": "AB", "name": "Alberta", "level":"province", "country": "CA"},
        {"code": "ABD", "name": "Aberdeenshire", "level":"council area", "country": "GB"},
        {"code": "AK", "name": "Alaska", "level":"state", "country": "US"}
    ]

    Olası çıktı:

    code,name,level,country
    NSW,New South Wales,state,AU
    AB,Alberta,province,CA
    ABD,Aberdeenshire,council area,GB
    AK,Alaska,state,US

    Olası çıktı:

    "code","name","level","country"
    "NSW","New South Wales","state","AU"
    "AB","Alberta","province","CA"
    "ABD","Aberdeenshire","council area","GB"
    "AK","Alaska","state","US"
  2. Giriş:

    [
        {"name": "bang", "value": "!", "level": 0},
        {"name": "letters", "value": "a,b,c", "level": 0},
        {"name": "letters", "value": "x,y,z", "level": 1},
        {"name": "bang", "value": "\"!\"", "level": 1}
    ]

    Olası çıktı:

    name,value,level
    bang,!,0
    letters,"a,b,c",0
    letters,"x,y,z",1
    bang,"""!""",0

    Olası çıktı:

    "name","value","level"
    "bang","!","0"
    "letters","a,b,c","0"
    "letters","x,y,z","1"
    "bang","""!""","1"

Üç artı yıl sonra ... bir jenerik json2csv, stackoverflow.com/questions/57242240/…
en yoğun

Yanıtlar:


160

İlk olarak, nesne dizisi girdinizdeki tüm farklı nesne özellik adlarını içeren bir dizi elde edin. Bunlar, CSV'nizin sütunları olacaktır:

(map(keys) | add | unique) as $cols

Ardından, nesne dizisi girdisindeki her nesne için, elde ettiğiniz sütun adlarını nesnedeki karşılık gelen özelliklerle eşleyin. Bunlar, CSV'nizin satırları olacaktır.

map(. as $row | $cols | map($row[.])) as $rows

Son olarak, sütun adlarını CSV için başlık olarak satırların önüne koyun ve elde edilen satır akışını @csvfiltreye aktarın.

$cols, $rows[] | @csv

Şimdi hep beraber. Sonucu -rham bir dize olarak almak için bayrağı kullanmayı unutmayın :

jq -r '(map(keys) | add | unique) as $cols | map(. as $row | $cols | map($row[.])) as $rows | $cols, $rows[] | @csv'

6
Çözümünüzün tüm özellik adlarını yalnızca birinciden ziyade tüm satırlardan alması çok hoş. Yine de çok büyük belgeler için bunun performans etkilerinin ne olduğunu merak ediyorum. Not: İsterseniz, $rowsdeğişken atamadan kurtulabilirsiniz :(map(keys) | add | unique) as $cols | $cols, map(. as $row | $cols | map($row[.]))[] | @csv
Jordan Running

9
Teşekkürler Jordan! $rowsBir değişkene atanması gerekmediğinin farkındayım ; Bunu bir değişkene atamanın açıklamayı daha güzel yaptığını düşündüm.

3
satır değerini dönüştürmeyi düşünün | iç içe diziler veya eşlemeler olması durumunda dize.
TJR

İyi bir öneri, @TJR. Belki iç içe geçmiş yapılar varsa, jq bunların içinde yinelenmeli ve değerlerini sütunlara dönüştürmelidir
LS

JSON bir dosyadaysa ve bazı belirli verileri CSV'ye filtrelemek isteseydiniz bu ne kadar farklı olurdu?
Neo

92

Bir deri bir kemik

jq -r '(.[0] | keys_unsorted) as $keys | $keys, map([.[ $keys[] ]])[] | @csv'

veya:

jq -r '(.[0] | keys_unsorted) as $keys | ([$keys] + map([.[ $keys[] ]])) [] | @csv'

Ayrıntılar

Bir yana

Ayrıntıları açıklamak zordur çünkü jq akışa yöneliktir, yani tek bir değer yerine bir JSON verisi dizisi üzerinde çalışır. Girdi JSON akışı, filtrelerden geçirilen ve daha sonra programın sonunda bir çıktı akışında kodlanan bazı dahili türe dönüştürülür. Dahili tür JSON tarafından modellenmez ve adlandırılmış bir tür olarak mevcut değildir. Çıplak dizinin ( .[]) veya virgül operatörünün çıktısını inceleyerek en kolay şekilde gösterilebilir (bunu doğrudan incelemek bir hata ayıklayıcı ile yapılabilir, ancak bu JSON'un arkasındaki kavramsal veri türleri yerine jq'nin dahili veri türleri açısından olacaktır) .

$ jq -c '. []' <<< '["a", "b"]'
"a"
"b"
$ jq -cn '"a", "b"'
"a"
"b"

Çıktının bir dizi olmadığını unutmayın (ki bu olabilir ["a", "b"]). Kompakt çıktı ( -cseçenek), her bir dizi öğesinin (veya ,filtreye ilişkin bağımsız değişkenin ) çıktıda ayrı bir nesne haline geldiğini (her birinin ayrı bir satırda olduğunu) gösterir.

Akış, JSON dizisine benzer , ancak kodlandığında çıktı ayırıcı olarak RS yerine satırsonu satırları kullanır . Sonuç olarak, bu dahili tip, bu cevapta "dizi" jenerik terimiyle anılır ve "akış", kodlanmış girdi ve çıktı için ayrılmıştır.

Filtrenin Oluşturulması

İlk nesnenin anahtarları şu şekilde çıkarılabilir:

.[0] | keys_unsorted

Anahtarlar genellikle orijinal sıralarında tutulur, ancak tam sıranın korunması garanti edilmez. Sonuç olarak, değerleri aynı sırayla almak için nesneleri indekslemek için kullanılmaları gerekecektir. Bu ayrıca, bazı nesnelerin farklı bir anahtar sırasına sahip olması durumunda değerlerin yanlış sütunlarda olmasını önleyecektir.

Hem anahtarları ilk satır olarak çıkarmak hem de indeksleme için kullanılabilir hale getirmek için, bunlar bir değişkende saklanır. Ardışık düzenin bir sonraki aşaması daha sonra bu değişkene başvurur ve başlığı çıkış akışının başına eklemek için virgül operatörünü kullanır.

(.[0] | keys_unsorted) as $keys | $keys, ...

Virgülden sonraki ifade biraz karmaşıktır. Bir nesne üzerindeki dizin operatörü, "name", "value"bu dizeler için bir özellik değerleri dizisi döndürerek bir dizi dizeyi alabilir (örneğin ). $keysbir dizi değil, bir dizidir, dolayısıyla []onu bir diziye dönüştürmek için uygulanır,

$keys[]

daha sonra aktarılabilir .[]

.[ $keys[] ]

Bu da bir dizi üretir, dolayısıyla dizi yapıcısı onu bir diziye dönüştürmek için kullanılır.

[.[ $keys[] ]]

Bu ifade, tek bir nesneye uygulanacaktır. map()bunu dış dizideki tüm nesnelere uygulamak için kullanılır:

map([.[ $keys[] ]])

Son olarak, bu aşama için bu, bir diziye dönüştürülür, böylece her öğe çıktıda ayrı bir satır olur.

map([.[ $keys[] ]])[]

Neden diziyi mapsadece dışarıda dağıtmak için bir dizi içinde paketleyelim ? mapbir dizi üretir; .[ $keys[] ]bir dizi üretir. Dizisine uygulamak map, .[ $keys[] ]bir dizi değer dizisi üretir, ancak diziler JSON türü olmadığından, bunun yerine tüm değerleri içeren düzleştirilmiş bir dizi elde edersiniz.

["NSW","AU","state","New South Wales","AB","CA","province","Alberta","ABD","GB","council area","Aberdeenshire","AK","US","state","Alaska"]

Her bir nesneden gelen değerlerin ayrı tutulması gerekir, böylece nihai çıktıda ayrı satırlar haline gelirler.

Son olarak, dizi @csvformatlayıcıdan geçirilir .

Alternatif

Öğeler erken değil geç ayrılabilir. Bir dizi elde etmek için virgül operatörünü kullanmak yerine (bir diziyi doğru işlenen olarak iletmek), başlık dizisi ( $keys) bir diziye sarılabilir ve +değerler dizisini eklemek için kullanılabilir. Bunun yine de aktarılmadan önce bir diziye dönüştürülmesi gerekiyor @csv.


3
İlk nesneden anahtar sırasını korumak keys_unsortedyerine kullanabilir misiniz keys?
Jordan

2
@outis - Akışlarla ilgili önsöz bir şekilde yanlış. Basit gerçek, jq filtrelerinin akış yönelimlidir. Diğer bir deyişle, herhangi bir filtre JSON varlıklarının akışını kabul edebilir ve bazı filtreler bir değer akışı oluşturabilir. Akıştaki öğeler arasında "yeni satır" veya başka bir ayırıcı yoktur - yalnızca yazdırıldıklarında bir ayırıcı devreye girer. Kendiniz görmek için şunu deneyin: jq -n -c 'indirgeme ("a", "b") as $ s ("";. + $ S)'
zirve

2
@peak - lütfen bunu yanıt olarak kabul edin, bu açık arayla en eksiksiz ve kapsamlı
btk

@btk - Soruyu sormadım ve bu yüzden kabul edemiyorum.
zirve

1
@Wyatt: Verilerinize ve örnek girdiye daha yakından bakın. Soru, tek bir nesne değil, bir dizi nesne hakkındadır. Deneyin [{"a":1,"b":2,"c":3}].
outis

6

Başlıklar ile csv'ye bir dizi nesne veya dizi çıktı veren bir işlev oluşturdum. Sütunlar, başlık sırasına göre olacaktır.

def to_csv($headers):
    def _object_to_csv:
        ($headers | @csv),
        (.[] | [.[$headers[]]] | @csv);
    def _array_to_csv:
        ($headers | @csv),
        (.[][:$headers|length] | @csv);
    if .[0]|type == "object"
        then _object_to_csv
        else _array_to_csv
    end;

Yani bunu şu şekilde kullanabilirsiniz:

to_csv([ "code", "name", "level", "country" ])

6

Aşağıdaki filtre, her değerin bir dizeye dönüştürülmesini sağlaması açısından biraz farklıdır. (Not: jq 1.5+ kullanın)

# For an array of many objects
jq -f filter.jq (file)

# For many objects (not within array)
jq -s -f filter.jq (file)

Filtrele: filter.jq

def tocsv($x):
    $x
    |(map(keys)
        |add
        |unique
        |sort
    ) as $cols
    |map(. as $row
        |$cols
        |map($row[.]|tostring)
    ) as $rows
    |$cols,$rows[]
    | @csv;

tocsv(.)

1
Bu, basit JSON için iyi çalışıyor, ancak birçok düzeyde aşağıya inen iç içe özelliklere sahip JSON ne olacak?
Amir

Bu elbette anahtarları sıralar. Ayrıca çıktısı uniqueyine de sıralanır, böylece unique|sortbasitleştirilebilir unique.
en yoğun

1
@TJR Bu filtreyi kullanırken, -rseçeneği kullanarak ham çıktıyı açmak zorunludur . Aksi takdirde, tüm alıntılar "geçersiz hale gelir ve bu, geçerli CSV değildir.
tosh

Amir: yuvalanmış özellikler CSV ile eşleşmez.
chrishmorris

2

Santiago'nun programının bu çeşidi de güvenlidir, ancak ilk nesnedeki anahtar adlarının, o nesnede göründükleri sırayla ilk sütun başlıkları olarak kullanılmasını sağlar:

def tocsv:
  if length == 0 then empty
  else
    (.[0] | keys_unsorted) as $keys
    | (map(keys) | add | unique) as $allkeys
    | ($keys + ($allkeys - $keys)) as $cols
    | ($cols, (.[] as $row | $cols | map($row[.])))
    | @csv
  end ;

tocsv
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.