jqを使用して任意の単純なJSONをCSVに変換する方法は?


105

jqを使用して、浅いオブジェクトの配列をエンコードする任意のJSONをCSVに変換するにはどうすればよいですか?

このサイトには、フィールドをハードコーディングする特定のデータモデルをカバーするQ&Aがたくさんありますが、この質問への回答は、JSONがあれば機能しますが、スカラープロパティを持つオブジェクトの配列(deep / complex /これらを平坦化することは別の問題であるため、サブオブジェクト。結果には、フィールド名を示すヘッダー行が含まれているはずです。最初のオブジェクトのフィールド順序を維持する回答が優先されますが、これは必須ではありません。結果は、すべてのセルを二重引用符で囲むか、引用符が必要なセルのみを囲む場合があります(例: 'a、b')。

  1. 入力:

    [
        {"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"}
    ]

    可能な出力:

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

    可能な出力:

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

    [
        {"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}
    ]

    可能な出力:

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

    可能な出力:

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

3年以上後...ジェネリックjson2csvstackoverflow.com/questions/57242240/にあります
ピーク

回答:


159

最初に、オブジェクト配列入力内のすべての異なるオブジェクトプロパティ名を含む配列を取得します。これらはCSVの列になります:

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

次に、オブジェクト配列入力の各オブジェクトについて、取得した列名をオブジェクトの対応するプロパティにマップします。それらはCSVの行になります。

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

最後に、列名を行の前に、CSVのヘッダーとして配置し、結果の行ストリームを@csvフィルターに渡します。

$cols, $rows[] | @csv

全部一緒に。-r結果を生の文字列として取得するには、フラグを使用することを忘れないでください。

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

6
ソリューションが最初の行だけでなく、すべての行からすべてのプロパティ名をキャプチャするのは素晴らしいことです。ただし、これがパフォーマンスに与える影響は、非常に大きなドキュメントの場合はどうでしょうか。あなたがしたい場合PSは、あなたは取り除くことができます$rowsそれをインライン化することにより、変数の代入:(map(keys) | add | unique) as $cols | $cols, map(. as $row | $cols | map($row[.]))[] | @csv
ヨルダンを実行して

9
ありがとう、ジョーダン!$rows変数に割り当てる必要がないことは承知しています。変数に割り当てると、説明がわかりやすくなると思いました。

3
行の値を変換することを検討してください。ネストされた配列またはマップがある場合の文字列。
TJR

@TJR、良い提案です。おそらくネストされた構造がある場合、jqはそれらに再帰し、それらの値も列にする必要があります
LS

JSONがファイル内にあり、特定のデータをCSVにフィルターで除外したい場合、これはどのように異なりますか?
Neo

91

スキニー

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

または:

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

詳細

さておき

jqはストリーム指向です。つまり、jqは単一の値ではなく、一連のJSONデータを操作するため、詳細を説明するのは難しいです。入力JSONストリームは、フィルターを介して渡される内部タイプに変換され、プログラムの最後で出力ストリームにエンコードされます。内部型はJSONでモデル化されておらず、名前付き型として存在しません。これは、ベアインデックス(.[])またはコンマ演算子の出力を調べることで最も簡単に示されます(デバッガーで直接調べることもできますが、JSONの背後にある概念的なデータ型ではなく、jqの内部データ型の観点から) 。

$ jq -c '。[]' <<< '["a"、 "b"]'
「あ」
「b」
$ jq -cn '"a"、 "b"'
「あ」
「b」

出力は配列ではないことに注意してください(これはになります["a", "b"])。コンパクトな出力(-cオプション)は、各配列要素(または,フィルターへの引数)が出力で個別のオブジェクトになることを示しています(それぞれが個別の行にあります)。

ストリームはJSON-seqに似ていますが、エンコード時にRSではなく改行を出力セパレータとして使用します。したがって、この内部タイプは、この回答では一般的な用語「シーケンス」によって参照され、「ストリーム」はエンコードされた入力と出力用に予約されています。

フィルターの作成

最初のオブジェクトのキーは次のようにして抽出できます:

.[0] | keys_unsorted

キーは通常、元の順序で保持されますが、正確な順序を維持することは保証されていません。したがって、同じ順序で値を取得するには、オブジェクトをインデックス付けするために使用する必要があります。これにより、一部のオブジェクトのキー順序が異なる場合に、値が誤った列に配置されることも防止されます。

キーを最初の行として出力し、インデックスに使用できるようにするために、キーは変数に格納されます。次に、パイプラインの次のステージでこの変数を参照し、カンマ演算子を使用してヘッダーを出力ストリームの前に付加します。

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

コンマの後の表現は少し複雑です。オブジェクトのインデックス演算子は、一連の文字列(例:)を取り、"name", "value"それらの文字列の一連のプロパティ値を返すことができます。$keys配列ではなく配列なので、配列[]に変換するために適用され、

$keys[]

次に渡されます .[]

.[ $keys[] ]

これもシーケンスを生成するため、配列コンストラクターを使用して配列に変換します。

[.[ $keys[] ]]

この式は、単一のオブジェクトに適用されます。map()これを外部配列のすべてのオブジェクトに適用するために使用されます。

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

最後に、このステージでは、これがシーケンスに変換されるため、各アイテムは出力の個別の行になります。

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

シーケンスを内の配列にバンドルして、mapそれを外でバンドル解除するのはなぜですか?map配列を作成します。.[ $keys[] ]シーケンスを生成します。mapからのシーケンスに適用すると、.[ $keys[] ]値のシーケンスの配列が生成されますが、シーケンスはJSONタイプではないため、代わりにすべての値を含むフラット化された配列を取得します。

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

各オブジェクトの値は、最終出力で別々の行になるように、別々に保つ必要があります。

最後に、シーケンスは@csvフォーマッタを介して渡されます。

代わりの

アイテムは、早期ではなく後期に分離できます。シーケンスを取得するためにコンマ演算子を使用する(シーケンスを右のオペランドとして渡す)代わりに、ヘッダーシーケンス($keys)を配列にラップして、+値の配列を追加するために使用できます。これは、に渡される前にシーケンスに変換する必要があります@csv


3
keys_unsorted代わりに使用keysして、最初のオブジェクトのキーの順序を保持できますか?
ジョーダン

2
@outis-ストリームに関する前文はやや不正確です。単純な事実は、jqフィルターがストリーム指向であることです。つまり、どのフィルターもJSONエンティティのストリームを受け入れることができ、一部のフィルターは値のストリームを生成できます。ストリーム内のアイテム間には、「改行」やその他のセパレータはありません。セパレータが導入されるのは、それらが印刷されたときだけです。自分のために参照するには、試してみる:JQ -n -c '$ sと( ""、 "B")を減らす( ""; + $ S。)'
ピーク

2
@peak-これを答えとして受け入れてください。これは、はるかに完全で包括的なものです
btk

@btk-私は質問をしなかったので、それを受け入れることができません。
ピーク

1
@Wyatt:データと入力例を詳しく見てみましょう。問題は、単一のオブジェクトではなく、オブジェクトの配列についてです。お試しください[{"a":1,"b":2,"c":3}]
outis '25年

6

オブジェクトの配列またはヘッダー付きのcsvに配列を出力する関数を作成しました。列はヘッダーの順序になります。

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;

したがって、次のように使用できます。

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

6

次のフィルターは、すべての値が文字列に確実に変換されるという点で少し異なります。(注:jq 1.5+を使用してください)

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

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

フィルタ: 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
これは単純なJSONには有効ですが、ネストされたプロパティが多くのレベルに達するJSONはどうでしょうか
アミール

もちろんこれはキーをソートします。の出力もuniqueとにかくソートされているので、unique|sortに簡略化できますunique
ピーク

1
@TJRこのフィルターを使用する-r場合、オプションを使用してraw出力をオンにする必要があります。そうしないと、すべての引用符"が余分にエスケープされますが、これは有効なCSVではありません。
tosh

Amir:ネストされたプロパティはCSVにマップされません。
chrishmorris

2

このSantiagoプログラムのバリアントも安全ですが、最初のオブジェクトのキー名が、そのオブジェクトに表示されるのと同じ順序で最初の列ヘッダーとして使用されることを保証します。

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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.