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


105

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"
    

세 플러스 년 후 ... 일반은 json2csv에있다 stackoverflow.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
솔루션이 첫 번째 행이 아닌 모든 행에서 모든 속성 이름을 캡처하는 것이 좋습니다. 그러나 이것이 매우 큰 문서에 대한 성능 영향이 무엇인지 궁금합니다. 추신 원하는 경우, $rows그냥 인라인 하여 변수 할당을 제거 할 수 있습니다 .(map(keys) | add | unique) as $cols | $cols, map(. as $row | $cols | map($row[.]))[] | @csv
Jordan Running

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는 스트림 지향적이므로 단일 값이 아닌 일련의 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.


3
keys_unsorted대신 keys첫 번째 개체의 키 순서를 유지하는 데 사용할 수 있습니까 ?
Jordan Running

2
@outis-스트림에 대한 서문이 다소 부정확합니다. 간단한 사실은 jq 필터가 스트림 지향적이라는 것입니다. 즉, 모든 필터는 JSON 엔터티 스트림을 허용 할 수 있으며 일부 필터는 값 스트림을 생성 할 수 있습니다. 스트림의 항목 사이에는 "새 줄"이나 다른 구분 기호가 없습니다. 인쇄 될 때만 구분 기호가 도입됩니다. 직접 확인하려면 다음을 시도하십시오. jq -n -c 'reduce ( "a", "b") as $ s ( "";. + $ s)'
정점 인

2
@peak - 그것은 지금까지 가장 완벽하고 포괄적 인 것입니다, 대답 함을 양해 해 주시기 바랍니다
BTK

@btk-질문을하지 않았기 때문에 받아 들일 수 없습니다.
피크

1
@Wyatt : 데이터와 예제 입력을 자세히 살펴보십시오. 문제는 단일 객체가 아닌 객체 배열에 관한 것입니다. 시도해보십시오 [{"a":1,"b":2,"c":3}].
outis

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은 어떻습니까?
Amir

물론 이것은 키를 정렬합니다. 또한, 출력 unique되어 있으므로, 어쨌든 정렬 unique|sort로 단순화 될 수있다 unique.
정점

1
@TJR이 필터를 사용할 때 -r옵션을 사용하여 원시 출력을 켜야 합니다. 그렇지 않으면 모든 따옴표 "가 유효한 CSV가 아닌 여분의 이스케이프 처리됩니다.
tosh

Amir : 중첩 된 속성은 CSV에 매핑되지 않습니다.
chrishmorris 19

2

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

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.