web-dev-qa-db-ja.com

jqを使用して任意のシンプルなJSONをCSVに変換するにはどうすればよいですか?

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

このサイトには、フィールドをハードコーディングする特定のデータモデルをカバーするQ&Aがたくさんありますが、この質問に対する答えは、スカラープロパティ(深層/複雑/サブオブジェクト、これらを平坦化することは別の質問です)。結果には、フィールド名を示すヘッダー行が含まれている必要があります。最初のオブジェクトのフィールドの順序を保持する回答が優先されますが、これは必須ではありません。結果は、すべてのセルを二重引用符で囲むか、引用符が必要なセルのみを囲む場合があります(例: '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"
    
76
outis

最初に、オブジェクト配列入力ですべての異なるオブジェクトプロパティ名を含む配列を取得します。これらは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'
121
user3899165

スキニー

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

または:

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

詳細

さておき

Jqはストリーム指向であり、単一の値ではなくJSONデータのシーケンスで動作するため、詳細の記述は注意が必要です。入力JSONストリームは、フィルターを通過する内部タイプに変換され、プログラムの最後で出力ストリームにエンコードされます。内部タイプはJSONによってモデル化されておらず、名前付きタイプとして存在しません。ベアインデックス(.[])またはカンマ演算子(デバッガで直接調べることができますが、それは概念的なものではなくjqの内部データ型の観点からの出力を調べることで最も簡単に実証できますJSONの背後にあるデータ型)。

 $ jq -c '。[]' <<< '["a"、 "b"]' 
 "a" 
 "b" 
 $ jq -cn '"a"、 "b"' 
 "a" 
 "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に渡す前に、これをシーケンスに変換する必要があります。

59
outis

次のフィルターは、すべての値が文字列に変換されることを保証するという点でわずかに異なります。 (注: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(.)
4
TJR

オブジェクトの配列または配列をヘッダー付きの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" ])
3
Jeff Mercado

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
1
peak