[json] jq를 사용하여 임의의 간단한 JSON을 CSV로 변환하는 방법은 무엇입니까?

jq를 사용하여 얕은 객체 배열을 인코딩하는 임의의 JSON을 어떻게 CSV로 변환 할 수 있습니까?

이 사이트에는 필드를 하드 코딩하는 특정 데이터 모델을 다루는 많은 Q & A가 있지만,이 질문에 대한 답변은 JSON이 주어 졌을 때 작동해야합니다. 단, 스칼라 속성이있는 객체의 배열이라는 제한이 있습니다 (딥 / 복합 / 이를 평평하게 만드는 것은 또 다른 질문입니다). 결과는 필드 이름을 제공하는 헤더 행을 포함해야합니다. 첫 번째 개체의 필드 순서를 유지하는 답변이 선호되지만 필수 사항은 아닙니다. 결과는 모든 셀을 큰 따옴표로 묶거나 인용이 필요한 셀만 묶을 수 있습니다 (예 : ‘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"
    


답변

먼저, 객체 배열 입력에서 모든 다른 객체 속성 이름을 포함하는 배열을 가져옵니다. 다음은 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'


답변

스키니

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으로 모델링되지 않으며 명명 된 유형으로 존재하지 않습니다. 베어 인덱스 ( .[]) 또는 쉼표 연산자 의 출력을 검사하여 가장 쉽게 설명 할 수 있습니다 (디버거로 직접 검사 할 수 있지만 JSON 뒤에있는 개념적 데이터 유형이 아닌 jq의 내부 데이터 유형 측면에서). .

$ jq -c '. []'<<< '[ "a", "b"]'
"ㅏ"
"비"
$ jq -cn ' "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.


답변

헤더가있는 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" ])


답변

다음 필터는 모든 값이 문자열로 변환된다는 점에서 약간 다릅니다. (참고 : 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(.)


답변

이 산티아고 프로그램의 변형도 안전하지만 첫 번째 개체의 키 이름이 해당 개체에 나타나는 것과 동일한 순서로 첫 번째 열 머리글로 사용되도록합니다.

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


답변