jq로 JSON 다루기 — 터미널에서 API 응답 파싱하기
curl로 받은 JSON을 눈으로 읽고 있었다면, jq 하나를 배우면 세상이 달라진다. 나는 거의 매일 jq를 사용한다. API 응답 확인부터 데이터 변환까지, 모든 것이 가능하다.
jq 설치 및 기초
# macOS
brew install jq
# Ubuntu/Debian
sudo apt-get install jq
# 검증
jq --version
가장 기본적인 사용법
# JSON을 pretty print
echo '{"name":"John","age":30}' | jq .
# 특정 필드만 추출
echo '{"name":"John","age":30}' | jq .name
기본 필터
필드 접근
# 단일 필드
jq '.name' user.json
# 중첩된 필드
jq '.user.address.city' user.json
# 선택적 접근 (필드가 없을 수도 있음)
jq '.user.phone?' user.json
# 배열 인덱싱
jq '.items[0]' data.json
# 마지막 요소
jq '.items[-1]' data.json
배열 순회
# 배열의 모든 요소 순회
jq '.items[]' data.json
# 필터 결합
jq '.items[] | .name' data.json
파이프 연산자
파이프는 왼쪽의 출력을 오른쪽의 입력으로 전달한다.
# 여러 필터를 조합
jq '.user | .name' data.json
# 더 복잡한 파이프라인
jq '.items[] | .user | .name' data.json
배열 연산
map - 배열의 각 요소에 함수 적용
# 모든 사용자의 이름만 추출
jq '.users | map(.name)' data.json
# 더 복잡한 변환
jq '.users | map({name: .name, email: .email})' data.json
select - 조건에 맞는 요소 필터링
# 나이가 30 이상인 사용자만
jq '.users[] | select(.age >= 30)' data.json
# 특정 역할의 사용자만
jq '.users[] | select(.role == "admin")' data.json
# 여러 조건 조합
jq '.users[] | select(.age >= 30 and .role == "admin")' data.json
sort_by - 정렬
# 나이 기준으로 정렬
jq '.users | sort_by(.age)' data.json
# 역순 정렬
jq '.users | sort_by(.age) | reverse' data.json
# 여러 기준으로 정렬
jq '.users | sort_by(.age, .name)' data.json
group_by - 그룹화
# 역할별로 그룹화
jq '.users | group_by(.role)' data.json
# 그룹화한 후 각 그룹의 크기 계산
jq '.users | group_by(.role) | map({role: .[0].role, count: length})' data.json
객체 생성 및 변환
객체 구성
# 새로운 객체 생성
jq '.users[] | {name, email}' data.json
# 필드명 변경
jq '.users[] | {user_name: .name, user_email: .email}' data.json
# 계산된 필드
jq '.products[] | {name, price, tax: (.price * 0.1)}' data.json
배열을 객체로 변환
# 배열을 key-value 맵으로 변환
jq 'map({key: .id, value: .name}) | from_entries' data.json
# 또는
jq 'reduce .[] as $item ({}; .[$item.id | tostring] = $item.name)' data.json
조건부 로직
if-then-else
# 나이에 따라 분류
jq '.users[] | {name, age, category: (if .age < 18 then "미성년자" elif .age < 65 then "성인" else "노년층" end)}' data.json
조건부 필터링
# status가 "active"이면 포함, 아니면 empty (결과에서 제외)
jq '.items[] | if .status == "active" then . else empty end' data.json
내장 함수
length - 길이
# 배열의 길이
jq '.users | length' data.json
# 문자열의 길이
jq '.name | length' data.json
keys와 values
# 객체의 모든 키
jq 'keys' data.json
# 객체의 모든 값
jq 'values' data.json
# 키-값 쌍
jq 'to_entries' data.json
type - 타입 확인
# 값의 타입 반환
jq '.value | type' data.json
# 타입 필터링
jq '.[] | select(type == "number")' data.json
has - 필드 존재 확인
# email 필드가 있는지 확인
jq '.users[] | select(has("email"))' data.json
실전 예제
GitHub API로 저장소 정보 가져오기
# 사용자의 모든 저장소 이름 출력
curl -s https://api.github.com/users/torvalds/repos | jq '.[].name'
# 별이 많은 저장소 순서로 정렬
curl -s https://api.github.com/users/torvalds/repos | jq 'sort_by(.stargazers_count) | reverse | .[0:5] | .[] | {name, stars: .stargazers_count}'
# 언어별 저장소 그룹화
curl -s https://api.github.com/users/torvalds/repos | jq 'group_by(.language) | map({language: .[0].language, count: length})'
AWS CLI 출력 파싱
# EC2 인스턴스 목록에서 ID와 상태만 추출
aws ec2 describe-instances | jq '.Reservations[].Instances[] | {InstanceId, State: .State.Name}'
# 실행 중인 인스턴스만 필터링
aws ec2 describe-instances | jq '.Reservations[].Instances[] | select(.State.Name == "running") | .InstanceId'
JSON 로그 분석
# 파일에서 모든 에러 로그 추출 (각 줄이 JSON)
cat app.log | jq 'select(.level == "error")'
# 에러 메시지와 타임스탐프만 추출
cat app.log | jq 'select(.level == "error") | {timestamp, message}'
# 시간대별로 에러 개수 세기
cat app.log | jq 'select(.level == "error") | .timestamp' | sort | uniq -c
고급 기능
reduce - 복합 계산
reduce는 배열을 순회하면서 누적값을 계산한다.
# 모든 가격의 합계
jq 'reduce .items[] as $item (0; . + $item.price)' data.json
# 사용자별로 주문 합계
jq 'reduce .orders[] as $order ({}; .[$order.user_id | tostring] = (. + $order.amount))' data.json
재귀적 탐색
.. 를 사용하면 모든 중첩된 값에 접근할 수 있다.
# 객체 내의 모든 "id" 필드 찾기
jq '.. | .id?' data.json
스트링 연산
# 문자열 연결
jq '.first_name + " " + .last_name' data.json
# 문자열 분할
jq '.email | split("@")[0]' data.json
# 정규표현식 매칭
jq '.email | test("^[^@]+@gmail\.com$")' data.json
# 정규표현식으로 추출
jq '.email | capture("(?[^@]+)@(?.+)")' data.json
팁과 트릭
다중 입력 처리
# 여러 JSON 파일 처리
jq -s '.' file1.json file2.json # 배열로 변환
# 또는
jq -s 'add' file1.json file2.json # 객체들을 병합
Raw output
# JSON 따옴표 없이 텍스트로 출력
jq -r '.name' data.json
Compact output
# 한 줄로 출력
jq -c '.' data.json
색상 출력 비활성화
# 파일로 리다이렉트할 때 유용
jq -M '.' data.json
성능 팁
큰 데이터셋을 다룰 때는 효율성이 중요하다.
# 스트리밍 모드 (한 번에 모든 데이터를 메모리에 로드하지 않음)
jq --stream '.' large_file.json
# select 대신 map 사용 (더 효율적)
jq 'map(select(.active))' data.json # 더 나음
jq '.[] | select(.active)' data.json # 덜 효율적
# 불필요한 map 제거
jq '.[].name' data.json # 충분함
jq '.[] | map(.name)' data.json # 불필요하게 복잡
jq는 처음에는 어려워 보이지만, 자주 사용하다 보면 강력함을 느낀다. 터미널에서 JSON을 다루는 작업이 얼마나 쉬워질 수 있는지 경험해보자.