# json의 가치를 높이는 커맨드라인 도구들

json은 웹 개발은 물론이고 REST API 개발, 쿠버네티스 상태 파악 자동화, 엘라스틱서치 쿼리 작성 등 정말 다양한 분야에서 특유의 유연성으로 사랑받고 있습니다. 하지만 엄격한 스키마를 따르지 않기 때문에 처음 접하는 형식의 json 데이터를 받았을 때 의미를 한눈에 파악하기는 쉽지 않습니다. 이번 글에서는 커맨드라인 도구들을 이용해서 낯선 json을 알아가는 과정을 이야기하겠습니다.

# 데이터 소스: JSONPlaceholder

예제에 사용할 json 형태의 데이터 소스가 필요합니다. 오픈 API를 이용하거나 여러가지 유명한 데이터셋을 사용하는 방법도 있지만, 여기서는 간편하게 JSONPlaceholder (opens new window)을 사용하겠습니다. JSONPlaceholder은 API 개발에 필요한 가짜 데이터를 제공합니다.

설명이 길어지지 않기 위해 리소스 종류 중에서 /posts만 사용하겠습니다. 이 글이 도움이 되신다면 다른 리소스(/comments, /albums, /photos, /todos, users)로도 실습해보는 것을 추천합니다.

HTTP 메소드는 GET 만 사용하겠습니다. 아주 짧게 설명하면 /posts는 글 전체 목록, /posts/{id} 는 해당 id를 가지는 글의 내용을 가져옵니다. 아래 링크를 클릭해서 어떤 내용인지 확인해보세요.

# 사용할 도구들. jq, jid, gron

  • jq: 개인적으로 가장 활용도가 어마무지하게 높은 유틸리티입니다. query 기능이 뛰어납니다. 링크 (opens new window)
  • jid: 인터렉티브한 질의를 통해 원하는 요소까지의 경로를 확인할 때 편합니다. 링크 (opens new window)
  • gron: 모든 경로를 펼쳐서 표시해줍니다. 배열 요소의 경우에 인덱스까지 함께 표시합니다. 링크 (opens new window)

# json 훑어보기

우선 jq로 시작해보겠습니다. jq를 처음 사용하게 된 것은 단순히 눈으로 보기 좋게 형식과 색상을 표현해주기 때문이었는데, 조금씩 질의언어를 잘 사용하게 되면서 bash script 같은 각종 스크립트 안에서 조합하는 형태로 사용하고 있습니다. json 전체 내용을 잘 구분된 들여쓰기와 색상으로 확인하고 싶다면 jq로 간단하게 할 수 있습니다. 아래 예시는 curl을 이용해서 HTTP GET 명령으로 받아온 json 응답을 파이프 | 를 통해 jq에 전달한 결과입니다. 총 100개의 글 정보가 표시되기 때문에 꽤 긴 결과가 표시됩니다.

$ curl -s https://jsonplaceholder.typicode.com/posts | jq
[
  {
    "userId": 1,
    "id": 1,
    "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
    "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
  },
  {
    "userId": 1,
    "id": 2,
    "title": "qui est esse",
    "body": "est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla"
  },
  ...
]

이번엔 글 제목 (title) 만 가져오고 싶다고 합시다. 이를 위해 우선 해야할 일은 title 항목에 어떤 경로로 도달할 수 있는지 확인하는 것입니다. 이번 예제는 단순해서 바로 객체의 배열 이라는 것을 알 수 있지만 중첩된 구조의 경우 경로를 한 눈에 파악하는 것이 어려울 수 있습니다. 그럴 때 이용할 수 있는 것이 gronjid 입니다. 우선 gron을 이용해보겠습니다. gron 의 결과는 2번줄을 보면 json = [] 이기 때문에 이 json은 배열이란 것을 알 수 있습니다. 3번줄에서 첫 번째 요소가 객체 임을 알 수 있습니다. json[0] = {}. 그리고 6번째 줄에서 우리가 원하는 항목 title을 발견할 수 있습니다. 경로는 json[0].title 이네요.

$ gron http://jsonplaceholder.typicode.com/posts
json = [];
json[0] = {};
json[0].body = "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto";
json[0].id = 1;
json[0].title = "sunt aut facere repellat provident occaecati excepturi optio reprehenderit";
json[0].userId = 1;
json[1] = {};
json[1].body = "est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla";
json[1].id = 2;
json[1].title = "qui est esse";
json[1].userId = 1;
json[2] = {};
json[2].body = "et iusto sed quo iure\nvoluptatem occaecati omnis eligendi aut ad\nvoluptatem doloribus vel accusantium quis pariatur\nmolestiae porro eius odio et labore et velit aut";
json[2].id = 3;
json[2].title = "ea molestias quasi exercitationem repellat qui ipsa sit aut";
json[2].userId = 1;

같은 과정을 jid를 통해 좀 더 interactive 하게 할 수 있습니다.

다시 jq 로 돌아오겠습니다. 위에서 gronjson[0].title 라는 경로를 jid.[0].title이라는 경로를 얻었습니다. 이제 이 경로를 jq의 인자로 넘겨줍니다.

$ curl -s https://jsonplaceholder.typicode.com/posts | jq '.[0].title'
"sunt aut facere repellat provident occaecati excepturi optio reprehenderit"

post의 title을 전부를 가져오려면 인덱스 (위에서는 0)을 빼고 실행합니다. 그럼 100줄의 결과를 얻을 수 있습니다.

$ curl -s https://jsonplaceholder.typicode.com/posts | jq '.[].title'
"sunt aut facere repellat provident occaecati excepturi optio reprehenderit"
"qui est esse"
"ea molestias quasi exercitationem repellat qui ipsa sit aut"
"eum et est occaecati"
"nesciunt quas odio"
...

너무 길죠? 그럼 첫 3개만 가져와보겠습니다. limit(n; exp) 함수를 사용합니다.

$ curl -s https://jsonplaceholder.typicode.com/posts | jq '[limit(3;.[])]'
[
  {
    "userId": 1,
    "id": 1,
    "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
    "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
  },
  {
    "userId": 1,
    "id": 2,
    "title": "qui est esse",
    "body": "est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla"
  },
  {
    "userId": 1,
    "id": 3,
    "title": "ea molestias quasi exercitationem repellat qui ipsa sit aut",
    "body": "et iusto sed quo iure\nvoluptatem occaecati omnis eligendi aut ad\nvoluptatem doloribus vel accusantium quis pariatur\nmolestiae porro eius odio et labore et velit aut"
  }
]

그리고, 파이프(|)를 이용해서 다시 title 만 얻습니다.

$ curl -s https://jsonplaceholder.typicode.com/posts | jq '[limit(3;.[])] | .[].title'
"sunt aut facere repellat provident occaecati excepturi optio reprehenderit"
"qui est esse"
"ea molestias quasi exercitationem repellat qui ipsa sit aut"

jq 는 엄청나게 다양한 함수를 제공하는데, 중간에 파이프(|)를 이용해서 다양한 조합의 결과를 만들 수 있습니다. 예를들어 얼마나 많은 사용자가 글을 썼는지 알기 위해서 유일한 userId 값을 찾아봅시다. 우선, titleuserId로 바꾸는 것 만으로 값을 얻을 수 있습니다.

$ curl -s https://jsonplaceholder.typicode.com/posts | jq '[limit(3;.[])] | .[].userId'
1
1
1

그럼, unique 함수를 이용해서 유일한 값들만 남기겠습니다. 이 경우에는 1만 남겠네요.

$ curl -s https://jsonplaceholder.typicode.com/posts | jq '[limit(3;.[])] | .[].userId | unique'
jq: error (at <stdin>:601): Cannot iterate over number (1)

이런, 에러가 발생했습니다. 숫자형은 iterate 할 수 없다고 합니다. unique는 배열을 인자로 받는데 위에 표시된 세 줄의 1숫자 값 이기 때문에 이런 에러가 발생했습니다. 그럼 우선, unique 앞의 과정의 결과를 배열로 바꾸어줍니다.

$ curl -s https://jsonplaceholder.typicode.com/posts | jq '[limit(3;.[])] | [.[].userId]'
[
  1,
  1,
  1
]

.[].userId 에서 [.[].userId] 으로 결과에 []를 씌워줌으로 배열로 바꾸는데 성공했습니다. 이제 다시 unique를 하면 에러가 수정되었음을 확인할 수 있습니다.

$ curl -s https://jsonplaceholder.typicode.com/posts | jq '[limit(10;.[])] | [.[].userId] | unique'
[
  1
]

이제 마지막으로 우리가 원하는 전체 posts 에 대한 결과를 확인하면 (limit을 지우면), 유일한 사용자는 총 10명인 것을 확인할 수 있습니다.

curl -s https://jsonplaceholder.typicode.com/posts | jq '[.[].userId] | unique'
[
  1,
  2,
  3,
  4,
  5,
  6,
  7,
  8,
  9,
  10
]

입력 json을 원하는 형태로 바꾸는 일도 매우 빈번하게 실행하는 일인데요, 이것도 프로그래밍 없이 간단하게 처리할 수 있습니다. 다음의 요구사항을 처리해보겠습니다.

  • idpost_id 라는 키로 바꾼다.
  • userIduser_id 라는 키로 바꾼다.
  • 그 외의 항목은 필요없다.
curl -s https://jsonplaceholder.typicode.com/posts | jq '[limit(3;.[])] | .[] | { post_id: .id, user_id: .userId }'
{
  "post_id": 1,
  "user_id": 1
}
{
  "post_id": 2,
  "user_id": 1
}
{
  "post_id": 3,
  "user_id": 1
}

매우 간단하면서도 유용해보이지 않습니까? 사실 jq는 너무 많은 함수가 있기 때문에 저도 필요할 때마다 찾아보고 있습니다. 혹시 이것도 있을까? 하고 찾아보면 있더라구요. json을 다뤄야 한다고 하면 우선 jq를 떠올려보시길 권해드립니다. jq manual (opens new window)

이 글이 여러분들께서 jq, jid, gron 같은 도구를 통해서 복잡하고 실수가 많은 json 처리에 생산성을 높이시는데 도움이 되시길 바랍니다.

Last Updated: 3/23/2020, 11:10:33 PM