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