Search

jq-descendants

JSON을 다루다 보면 객체가 중첩(nested), 재귀적인(recursive) 구조가 있다. 객체 안엔 자식 객체를 담는 배열 애트리뷰트가 있고, 그 배열이 비어 있거나 또는 애트리뷰트(= 키 자체)가 없는 경우까지 반복 된다:
{ "key1": "target", "key2": "val2", "children": [ { "key1": "target", "key2": "val4", "children": [ { "key1": "target", "key2": "val6", "children": [], "__comment": "더 이상 children이 없거나" }, { "key1": "val7", "key2": "val8" "__comment": "아예 children 키가 없다" }, ] }, { "key1": "val9", "key2": "val10" }, ]}
JSON
복사
이 때 특정 조건을 만족하는 객체만 필터해야 할 때가 있다. jq의 recursive descdent(..)를 사용하면 간단히 할 수 있다. key1의 값이 target인 객체만 필터해보자:
cat descendants.json | jq '.. | select(.key1? == "target")'{ "key1": "target", "key2": "val2", "children": [ { "key1": "target", "key2": "val4", "children": [ { "key1": "target", "key2": "val6", "children": [], "__comment": "더 이상 children이 없거나" }, { "key1": "val7", "key2": "val8", "__comment": "아예 children 키가 없다" } ] }, { "key1": "val9", "key2": "val10" } ]}{ "key1": "target", "key2": "val4", "children": [ { "key1": "target", "key2": "val6", "children": [], "__comment": "더 이상 children이 없거나" }, { "key1": "val7", "key2": "val8", "__comment": "아예 children 키가 없다" } ]}{ "key1": "target", "key2": "val6", "children": [], "__comment": "더 이상 children이 없거나"}
Shell
복사

Recursive Descent: ..

Recursively descends ., producing every value. This is the same as the zero-argument recurse builtin (see below). This is intended to resemble the XPath // operator. Note that ..a does not work; use ..|.a instead. In the example below we use ..|.a? to find all the values of object keys “a” in any object found “below” ..
..는 현재 입력(.)의 모든 자손(descendants)의 값을 출력한다. XPath를 써본 사람이라면, // 연산자와 비슷한 역할을 한다는 설명도 도움이 된다:
cat descendants.json | jq '..'{ "key1": "target", "key2": "val2", "children": [ { "key1": "target", "key2": "val4", "children": [ { "key1": "target", "key2": "val6", "children": [], "__comment": "더 이상 children이 없거나" }, { "key1": "val7", "key2": "val8", "__comment": "아예 children 키가 없다" } ] }, { "key1": "val9", "key2": "val10" } ]}"target""val2"[ { "key1": "target", "key2": "val4", "children": [ { "key1": "target", "key2": "val6", "children": [], "__comment": "더 이상 children이 없거나" }, { "key1": "val7", "key2": "val8", "__comment": "아예 children 키가 없다" } ] }, { "key1": "val9", "key2": "val10" }]{ "key1": "target", "key2": "val4", "children": [ { "key1": "target", "key2": "val6", "children": [], "__comment": "더 이상 children이 없거나" }, { "key1": "val7", "key2": "val8", "__comment": "아예 children 키가 없다" } ]}"target""val4"[ { "key1": "target", "key2": "val6", "children": [], "__comment": "더 이상 children이 없거나" }, { "key1": "val7", "key2": "val8", "__comment": "아예 children 키가 없다" }]{ "key1": "target", "key2": "val6", "children": [], "__comment": "더 이상 children이 없거나"}"target""val6"[]"더 이상 children이 없거나"{ "key1": "val7", "key2": "val8", "__comment": "아예 children 키가 없다"}"val7""val8""아예 children 키가 없다"{ "key1": "val9", "key2": "val10"}"val9""val10"
Shell
복사
루트 JSON의 모든 값을 순회하여 출력한다. 그래서 객체가 아닌 값(예제에선 문자열)을 만나면 .key1 키 인덱스 식별자에 respond 할 수 없어 에러가 난다:
cat descendants.json | jq '.. | .key1'"target"jq: error (at <stdin>:27): Cannot index string with string "key1"
Shell
복사
따라서 위와 같은 목적으로 사용할 땐 옵셔널 인덱스 식별자로 ?를 꼭 붙여주는 것이 좋다.

정리

nginx 설정 파일을 JSON으로 파싱해 정적 분석할 때 알게 된 내용이다. 이건 따로 정리해서 포스팅할 예정이다.
JSON 그리고 jq는 단순함의 미학이 있는데 막상 잘 사용을 못해서 직접 쓰면 열 받는다(?)(그리고 쓰고 나니, JSON이 ‘어떤 키에 특정 값 타입이 반복된다’ 하는 특정 스키마에 국한된 내용이 아니다. .. 연산자가 JSON 모든 값을 순회하고 키 식별자를 선택적(?)으로 lookup하기 때문이다).
항상 매뉴얼을 켜두고 쓰자. 또 지금처럼 연산자나 필터 함수에 대한 토막 내용을 정리하면 나아지리라 믿는다. 그리고 대충 내가 원하는걸 검색하면 스택오버플로에 비슷한 고민을 하는 사람들이 많다는걸 볼 수 있다. 거기서도 도움을 얻자!

참고

https://stedolan.github.io/jq/manual/#RecursiveDescent:..