Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.
RetroTech 팟캐스트 44BITS 팟캐스트

jq - 커맨드라인 JSON 프로세서

요즘은 API에서 데이터를 주고받을 때 JSON을 사용하는 것이 대부분이기 때문에 어떤 시스템과 연동하든 JSON 결과를 받아서 사용하는 경우가 많은데 서버에서 JSON 응답을 이쁘게 포매팅해서 보내준다면 보기가 쉽지만 압축해서 보내준다면 어떤 필드나 데이터가 있는지 한눈에 파악해서 보기가 어렵다. 코드에서 파싱할 때는 상관없지만, 필드를 확인해서 코드를 작성해야 하거나 디버깅을 해야 하는 경우에는 JSON을 이쁘게 출력해서 보아야 하는 경우가 많이 있다.

HTTP 요청을 보내고 결과도 포매팅해주는 도구를 사용하는 때도 많지만 개발하다 보면 그렇게 되지 않을 때도 있으므로 전에는 JSONLint 사이트를 이용해서 포매팅해서 결과를 확인했다. JSONLint는 JSON 데이터의 유효성 검사를 해주는 사이트이지만 유효성 검사를 할 때 포매팅도 해주므로 그 결과를 에디터 등에 복사해서 보면서 결과를 확인했다. 웹사이트이므로 어디서나 접속해서 사용할 수 있다는 장점도 있지만, 코드에서 사용하든 터미널에서 curl로 사용하든 결과를 복사해서 웹사이트를 열어서 복사한 뒤 결과를 사용해야 한다는 불편함이 존재했다.

웹브라우저에서 복잡합 JSON을 받는 요청을 확인한 화면

예를 들어 https://api.stackexchange.com/2.2/search?site=stackoverflow&order=desc&sort=activity&intitle=http&filter=default 같은 API를 사용할 때 응답으로 받은 JSON 데이터가 어떤 구조로 되어 있고 어떤 값이 있는지 찾기가 쉽지 않다.

python 이용하기

이 응답을 복사해서 앞에서 얘기한 JSONLint에서 포매팅을 해도 되지만 자주 사용하는 일은 커맨드라인에서 사용하면 좀 더 편하다.(이건 사람마다 다를지도...) 이 요청을 curl을 사용해서 터미널에서 사용한다면 다음과 같이 사용할 수 있다.(--compressed 옵션은 stackexchange에서 응답을 바이너리로 주어서 넣은 옵션이다.)

$ curl 'https://api.stackexchange.com/2.2/search?site=stackoverflow&order=desc&sort=activity&intitle=http&filter=default' --compressed
{"items":[{"tags":["javascript","node.js","tizen-web-app"],"owner":{"reputation":1,"user_id":6056094,"user_type":"registered","profile_image":"https://www.gravatar.com/avatar/dfb9005186ad27236eab774ae88aaca9?s=128&d=identicon&r=PG&f=1","display_name":"Yahav Zamari","link":"http://stackoverflow.com/users/6056094/yahav-zamari"},"is_answered":false,"view_count":17,"answer_count":0,"score":0,"last_activity_date":1457865787,"creation_date":1457865787,"question_id":35969238,"link":"http://stackoverflow.com/questions/35969238/node-js-http-request-does-nothing-using-http-request","title":"node JS HTTP request does nothing using http request"},{"tags":["python","multithreading","concurrency","multiprocessing"],"owner":{"reputation":558,"user_id":991710,"user_type":"registered","accept_rate":81,"profile_image":"https://www.gravatar.com/avatar/f660a9ad68d31e30b2d7b153f7eb5bc5?s=128&d=identicon&r=PG","display_name":"user991710","link":"http://stackoverflow.com/users/991710/user991710"},"is_answered":tru
.......뒷부분은 생략

python -m json.tool을 이용하면 JSON을 포매팅해서 사용할 수 있다.

$ curl 'https://api.stackexchange.com/2.2/search?site=stackoverflow&order=desc&sort=activity&intitle=http&filter=default' --compressed | python -m json.tool
{
    "has_more": true,
    "items": [
        {
            "answer_count": 0,
            "creation_date": 1457865787,
            "is_answered": false,
            "last_activity_date": 1457865787,
            "link": "http://stackoverflow.com/questions/35969238/node-js-http-request-does-nothing-using-http-request",
            "owner": {
                "display_name": "Yahav Zamari",
                "link": "http://stackoverflow.com/users/6056094/yahav-zamari",
                "profile_image": "https://www.gravatar.com/avatar/dfb9005186ad27236eab774ae88aaca9?s=128&d=identicon&r=PG&f=1",
                "reputation": 1,
                "user_id": 6056094,
                "user_type": "registered"
            },
            "question_id": 35969238,
            "score": 0,
            "tags": [
                "javascript",
                "node.js",
                "tizen-web-app"
            ],
            "title": "node JS HTTP request does nothing using http request",
            "view_count": 17
        },
.......뒷부분은 생략

curl 'URL' | python -m json.tool과 같이 CURL로 받은 응답을 파이프로 json.tool에 연결하면 JSON을 포매팅해서 볼 수 있다. 터미널에서는 포매팅 되어도 결과를 확인하거나 검색해 보기 어려우므로 에디터에서 복사해서 보려면 ``curl 'URL' | python -m json.tool | pbcopypbcopy`까지 연결하면 바로 클립보드에 복사되므로 에디터 등에 붙여넣어서 바로 볼 수 있다.

$ echo '{"foo": "lorem", "bar": "ipsum"}' | python -m json.tool
{
    "bar": "ipsum",
    "foo": "lorem"
}

웹 브라우저 등에서 JSON 결과를 받아서 이미 복사를 했다면 echo 명령어를 사용해서 위처럼 포매팅하는 것도 가능하다. 보통 python은 설치되어 있으므로 간단하게 JSON을 포매팅해서 볼 수 있는 유용한 방법이다.

jq

jq 로고

jq는 커맨드라인에서 JSON을 조작할 수 있는 도구이다. jq는 OS별로 바이너리를 제공하므로 자신의 OS에 맞는 버전을 설치하면 된다. OS X에서는 Homebrew를 이용해서 brew install jq로 설치할 수 있다. 설치가 완료되면 터미널에서 jq 명령어를 사용할 수 있다.

$ jq --version
jq-1.5

jq를 이용하면 앞에서 Python의 json.tool을 쓴 것과 같게 JSON을 포매팅할 수 있다.

$ echo '{"foo": "lorem", "bar": "ipsum"}' | jq .
{
  "foo": "lorem",
  "bar": "ipsum"
}

curl을 이용한다면 curl 'URL' | jq .과 같이 사용할 수 있고 바로 복사를 하려면 curl 'URL' | jq . | pbcopy와 같이 사용할 수 있다. jq 다음에 .이 있음을 유의해야 한다. 단순히 포매팅만 한다면 명령어가 좀 더 간단한 것 외에는 json.tool보다 좋은 점이 없지만 jq는 좀 더 다양한 기능을 제공하고 있다.

다음과 같은 data.json이 있다고 해보자.(앞의 stackexchange에서 받은 결과를 간단하게 만든 것이다.)

{
  "has_more": true,
  "items": [
    {
      "answer_count": 0,
      "creation_date": 1457869825,
      "is_answered": false,
      "owner": {
        "display_name": "trafalgar",
        "reputation": 171
      },
      "question_id": 35969871,
      "score": 0,
      "tags": [
        "cakephp",
        "cakephp-2.0"
      ],
      "title": "Accessing POST query parameters from HTTPS to HTTP",
      "view_count": 2
    },
    {
      "answer_count": 0,
      "creation_date": 1457869139,
      "is_answered": false,
      "owner": {
        "display_name": "Istv",
        "reputation": 1
      },
      "question_id": 35969759,
      "score": 0,
      "tags": [
        "android",
        "get",
        "httprequest"
      ],
      "title": "Android Http response shows after second click only",
      "view_count": 7
    }
  ]
}

앞에서 jq .과 같이 사용했을 때 .는 JSON 문서의 루트를 의미한다. 그래서 JSON 데이터를 탐색하듯이 루트에서 시작해서 원하는 데이터를 탐색해 볼 수 있다.

$ cat data.json | jq .has_more
true

$ cat data.json | jq .items[0].owner.display_name
"trafalgar"

$ cat data.json | jq .items[1].tags
[
  "android",
  "get",
  "httprequest"
]

$ cat data.json | jq .items[1].tags[]
"android"
"get"
"httprequest"

JSON 문서의 구조를 알고 있다면 복잡한 JSON 데이터에서 원하는 필드의 값을 바로 확인하는 것이 가능하다. .으로 시작해서 약간 어색해 보이지만 탐색 방법은 흔히 사용하는 방식이라 약간만 사용해 보면 쉽게 사용할 수 있다.

$ cat data.json | jq \
> '.items[] | {answer: .answer_count, owner: .owner.display_name, date: .creation_date}'
{
  "answer": 0,
  "owner": "trafalgar",
  "date": 1457869825
}
{
  "answer": 0,
  "owner": "Istv",
  "date": 1457869139
}

위처럼 기존 JSON의 데이터를 이용해서 다른 형식의 JSON 문서를 만드는 것도 가능하다.(\는 블로그에서 보기 편하게 줄을 나눈 것뿐이다.) 위에서는 item에 있는 배열에서 원하는 값만 추출해서 새로운 형식의 JSON을 만든 것이다. 간단한 명령어는 그냥 사용해도 되지만 문법이 약간 복잡해 지면 jq에 전달하는 전체 파라미터를 따옴표(')로 묶어 주어야 한다. jq의 파라미터 내에서도 파이프(|)를 많이 사용하기 때문에 처음에는 좀 헷갈린다.

$ cat data.json | jq \
> '[.items[] | {answer: .answer_count, owner: .owner.display_name, date: .creation_date}]'
[
  {
    "answer": 0,
    "owner": "trafalgar",
    "date": 1457869825
  },
  {
    "answer": 0,
    "owner": "Istv",
    "date": 1457869139
  }
]

배열로 만들어야 한다면 전체를 []로 감싸면 최종 결과도 배열로 나오게 된다.

커맨드라인에서 간단한 파서를 만든다면 필요할지도 모르지만 문서를 보면 아주 다양한 내장 함수를 포함하고 있어서 JSON 데이터를 원하는 대로 조작하고 탐색할 수 있다. 나는 아직 그 정도로 쓰고 있진 않고 API를 사용할 때 간단한 포매팅이나 원하는 값을 탐색하는 용도 정도로 쓰고 있다.

2016/03/13 22:27 2016/03/13 22:27