트랙
/
jq
jq
/
연습 문제
/
쇼핑 목록
쇼핑 목록

쇼핑 목록

학습 연습 문제

소개

기초

JSON 문법과 데이터 타입에 익숙하다고 가정할게요.

jq는 들어오는 JSON 데이터를 단일 표현식(_필터의 파이프라인_으로 작성)에 통과시켜 원하는 형태로 변환된 데이터를 얻는 방식으로 동작해요.

Note

jq 언어는 jq _프로그램_으로 구현되어 있어요. 이 프로그램은 입력이 어떻게 처리되고 출력이 어떻게 표시될지 제어하는 몇 가지 편리한 명령줄 옵션을 제공해요.

아래 예제에서 이런 옵션을 만나게 돼요:

  • -n 또는 --null-input

    보통은 jq 프로그램에 읽을 파일을 주거나, 입력으로 데이터를 보내요. --null-input 옵션을 사용하면 아무 입력 없이 JSON 데이터를 생성할 수 있어요.

  • -c 또는 --compact-output

    jq는 기본적으로 출력을 보기 좋게 정렬해서 출력해요. 데이터가 보기 좋게 서식이 맞춰져 있으면 사람이 보기에 아주 유용해요. 하지만 기계에게는 그럴 필요가 없어요. --compact-output 옵션은 서식용 공백을 제거해서 결과 JSON의 크기를 줄여줘요.

  • -f filename 또는 --from-file filename

    명령줄에서 직접 주는 대신 filename에서 jq 프로그램을 읽어요. sed와 awk도 같은 목적으로 -f 옵션을 사용해요. 연습 문제의 테스트 스크립트에서 이 옵션이 사용되는 것을 볼 수 있어요.

이 강의의 나머지 부분에서는 jq 언어 자체에 집중할게요.

필터와 파이프

필터는 표현식이라고도 해요.

_필터_는 입력을 받아 출력을 만들어요. 유닉스 셸에서 작업하는 방식처럼, 파이프 |로 _필터_를 이어서 한 _필터_의 출력을 다른 _필터_의 입력으로 연결할 수 있어요.

항등 필터: .

가장 단순한 _필터_예요. 입력을 그대로 출력으로 넘겨줄 뿐이에요.

$ echo '[1, 2, 3]' | jq '.'
[
  1,
  2,
  3
]

배열

배열을 다루는 방법을 빠르게 소개할게요. 이 주제는 나중에 더 자세히 다뤄요.

_배열_의 원소는 대괄호로 접근하며, 인덱스는 0부터 시작해요.

$ echo '[10, 20, 30]' | jq '.[1]'
20

_필터_는 표현식을 [와 ]로 감싸서 _배열_을 만들 수 있어요.

  • 알려진 원소 목록으로 만드는 경우:

    jq -n '[1, 2, 3]'
    
  • 원소의 스트림을 모으는 경우: 예를 들어, range는 숫자 스트림을 출력하는 함수예요

    $ jq -n 'range(10; 70; 15)'
    10
    25
    40
    55
    

    []를 사용하면 표현식의 결과를 배열로 모아요

    $ jq -c -n '[range(10; 70; 15)]'
    [10,25,40,55]
    

쉼표도 연산자예요

_쉼표_는 배열 원소를 구분하는 문법만이 아니에요. 쉼표는 스트림을 이어주는 연산자예요.

예를 들어 [1, 2, 3]은 1, 2, 3 세 표현식을 이어붙인 결과를 모으기 위해 배열 생성자 []를 사용하는 _필터_예요.

위의 range(10; 70; 15)에서 세미콜론을 눈치챘나요? jq 언어에서 _쉼표_는 특정한 용도가 있기 때문에, 여러 인자를 받는 함수는 인자를 구분할 때 세미콜론을 사용해요.

객체

객체를 빠르게 소개할게요.

많은 프로그래밍 언어와 비슷하게, 점(.)으로 객체 속성에 접근해요.

$ echo '{"foo": {"bar": "qux"}}' | jq '.foo.bar'
"qux"
Note

_객체_에도 대괄호를 사용할 수 있지만, 그럴 때는 문자열 리터럴에 따옴표가 필요해요. 공백이 들어간 키를 다루는 한 가지 방법이에요.

$ echo '{"foo bar": "qux"}' | jq '.["foo bar"]'
"qux"

{}와 key: value 쌍으로 _객체_를 만들 수 있어요. "단순한" 문자열인 키 주위에는 따옴표가 필요 없어요.

jq -n '{question: (6 * 9), answer: 42}'

는 다음을 출력해요

{
  "question": 54,
  "answer": 42
}

_키_를 _표현식_으로 다루려면 괄호로 감싸야 해요 (다음도 위와 같은 결과를 출력해요).

echo '[{"key":"question", "value":54}, {"key":"answer", "value":42}]' \
| jq '{(.[0].key): .[0].value, (.[1].key): .[1].value}'

파이프라인

예를 들어, 다음 내용이 담긴 file.json이 있다고 해요

{
  "key1": "value1",
  "key2": [5, 15, 25]
}

key2 _배열_의 길이를 계산해볼까요:

$ jq '.key2 | length' file.json
3

.key2 표현식의 출력을 length의 입력으로 _파이프_하고 있어요. 그러면 예상대로 _배열_의 원소 개수를 출력해요.

필터는 입력을 무시할 수 있어요

이 예제에서는 입력 JSON 데이터가 무시되고 출력에 아무 영향도 주지 않아요:

$ echo '{"answer": 42}' | jq '6 * 9'
54

필터는 데이터 스트림을 출력할 수 있어요

_필터_는 값을 하나 이상 출력할 수 있어요. 예를 들어 .[] _필터_는 _배열_의 각 원소를 별개의 값으로 출력해요:

$ jq -n -c '[1, 2, 3]'
[1,2,3]

$ jq -n -c '[1, 2, 3] | .[]'
1
2
3

이런 _필터_를 다른 필터로 파이프하면 두 번째 _필터_가 각 값마다 실행돼요:

$ jq -n -c '[1, 2, 3] | .[] | . * 2'
2
4
6

암시적 반복과 비슷해요. 이 기법을 이해하고 나면, 아주 강력한 jq _필터_를 아주 간결하게 작성할 수 있다는 걸 알게 될 거예요.

괄호

괄호는 다른 언어에서와 마찬가지로 연산 순서를 강제하기 위해 하위 표현식을 묶는 데 사용해요. jq에서는 이 괄호가 필요해 보이는 상황이 다소 의외로 느껴질 수 있어요.

예를 들어, 원소가 두 개인 _배열_을 만들고 싶다고 해요. 9의 제곱근, 그리고 _e_의 1제곱이에요. 두 개별 표현식은 9 | sqrt와 1 | exp예요. 기대하는 출력은 배열 [3, 2.7...]이에요.

$ jq -n '[ 9|sqrt, 1|exp ]'
[
  20.085536923187668,
  2.718281828459045
]

왜 기대한 결과를 얻지 못했을까요? jq는 저 식을 이렇게 해석해요:

[ ((9|sqrt), 1) | exp ]

jq는 두 원소(3과 1)의 스트림을 만들고, 각각을 exp에 넘겨요.

exp가 숫자 하나만 입력으로 받도록 해야 해요. 다시 말해, 쉼표보다 파이프가 먼저 평가되도록 강제해야 해요.

$ jq -n '[ 9|sqrt, (1|exp) ]'
[
  3,
  2.718281828459045
]

함수와 연산자

깊이 들어가지는 않고(함수는 다른 연습 문제에서 다룰 주제예요), 유용한 내장 함수 몇 가지를 소개할게요:

  • length

    배열을 입력으로 받으면 배열의 원소 개수를 출력해요.

    $ jq -n '[10, 20, 30, 40] | length'
    4
    
  • +

    이 연산자는 피연산자의 타입에 따라 하는 일이 달라요. 숫자는 더하고, 문자열은 이어붙이고, 배열은 덧붙이고, 객체는 병합해요.

    $ jq -c -n '
        3 + 4,
        "foo" + "bar",
        ["a", "b"] + ["c"],
        {"m": 10} + {"n": 20}
    '
    7
    "foobar"
    ["a","b","c"]
    {"m":10,"n":20}
    

    add는 배열을 받아 모든 원소를 +의 규칙으로 더한 항목을 반환하는 함수예요. [1, 2, 3] | add는 6을 출력해요.

  • map

    배열을 입력으로, 필터를 인자로 받아, 각 원소에 필터를 적용한 배열을 출력해요

    $ jq -c -n '[10, 20, 30, 40] | map(. / 5)'
    [2,4,6,8]
    
  • select

    어떤 입력과 필터를 인자로 받아요:

    • 인자에 적용한 필터가 참 값을 내면 입력을 그대로 출력해요
    • 그렇지 않으면 아무것도 출력하지 않아요 (null 값이 아니라, 정말 아무 출력도 없어요)

    예를 들어, 어떤 숫자들 중에서 3으로 나누어 떨어지는 것을 골라봐요

    $ jq -n 'range(10) | select(. % 3 == 0)'
    0
    3
    6
    9
    

    range는 숫자의 _스트림_을 출력한다는 걸 기억해요. select는 각 숫자마다 한 번씩 호출돼요. 표현식을 "통과"하는 숫자만 출력돼요.

    배열의 원소를 골라내야 할 때가 자주 있어요. 이를 위한 방법이 몇 가지 있어요.

    입력이 ["Anne", "Bob", "Cathy", "Dave"]일 때, 길이가 4인 이름을 골라봐요.

    • map과 select를 함께 사용하기

      map(select(length == 4))
      
    • 배열을 원소로 펼치고, 그 스트림에 select를 적용한 뒤, 결과를 모으기

      [ .[] | select(length == 4) ]
      

주석

주석은 # 문자로 시작해서 줄 끝까지 이어져요.

지침

문득 팬케이크가 먹고 싶어졌어요. 기술에 관심이 많은 현대인답게, 팬케이크 재료 쇼핑 목록을 JSON 형식으로 작성해요. 목록의 구조는 다음과 같아요:

{
  "name": "name of shopping list",
  "ingredients": [ ...list of ingredients... ],
  "optional ingredients": [ ...list of ingredients... ]
}

재료는 다음과 같이 JSON 객체로 표현해요:

{
  "item": "flour",
  "amount": {
    "quantity": 2,
    "unit": "cup"
  }
}

재료에는 "substitute"라는 선택적 속성이 있을 수 있는데, 여기에는 대신 사용할 수 있는 다른 재료의 이름을 담은 문자열이 들어 있어요.

jq로 쇼핑 목록을 어떻게 살펴볼 수 있는지 알아봐요.

1. 쇼핑 목록 이름 추출하기

쇼핑 목록의 "name" 요소를 출력하는 표현식을 작성해요.

2. 필요한 재료가 몇 개인지 세기

"required" 재료만 세요. 선택적 재료는 세지 않아요.

3. 설탕은 얼마나 필요할까요?

설탕의 양을 출력하는 표현식을 작성해요. 숫자 부분만 필요해요.

4. 대체할 수 있는 재료 매핑하기

일부 재료는 대체할 수 있어요 (재료 X가 없으면 Y를 사용할 수 있어요). 추천 재료를 그 대체 재료에 매핑한 JSON 객체를 출력해요. 매핑에 선택적 재료도 포함해요.

GitHub에서 편집 링크가 새 창이나 탭에서 열려요
jq Exercism

쇼핑 목록 문제를 시작해 볼 준비가 됐나요?

Exercism에 가입하고 jq 트랙을 개념 12개연습 문제 75개, 그리고 실제 사람의 멘토링과 함께 배우고 익혀 보세요. 모두 무료예요.