JMESPath 입문: JSONPath를 이기는 프로젝션 쿼리
핵심 요약: JMESPath는 객체 묶음을 JSONPath보다 직접적으로 재구성합니다. 문법, 프로젝션, 파이프, 내장 함수, AWS CLI 사용법을 다루는 입문 가이드입니다.

핵심부터 말하면, 위치로 찾을 때는 JSON Pointer, 필터나 재귀에는 JSONPath, 결과를 재구성하거나 명령행에서 프로젝션할 때는 JMESPath를 쓰세요. 아는 필드 하나만 읽는다면 어느 것도 필요 없습니다.
목표가 객체 묶음을 다른 모양으로 재구성하는 것이라면 JMESPath가 JSONPath보다 직접적인 경우가 많습니다. 노드를 찾는 것이 아니라 결과를 구성합니다.
1. JMESPath란
JMESPath는 JSON을 위해 특별히 설계된 쿼리 언어입니다. 원래 AWS CLI 출력 필터링을 위해 만들어진 뒤 jmespath.js, jmespath.py, go-jmespath 같은 구현을 통해 여러 언어로 퍼졌습니다. 목표가 JSONPath와 다릅니다:
- JSONPath는 경로와 조건으로 노드 집합을 선택하고 노드 집합을 반환하는 데 집중합니다.
- JMESPath는 입력 JSON을 변환하고 어떤 모양의 값이든 반환하는 데 집중합니다.
이 차이가 JMESPath를 목록 프로젝션, 필드 이름 변경, 결과 트리밍에 특히 좋게 만듭니다. 예: API 응답의 스무 필드를 프런트엔드가 실제로 필요로 하는 세 필드로 줄이기. 아직 JSONPath 기초를 읽지 않았다면 JSONPath 쿼리 언어 실전부터 시작하세요.
2. 기본 문법: 식별자에서 인덱스까지
JMESPath는 JSONPath보다 기호가 적고 추출 표현식처럼 읽힙니다:
| 문법 | 의미 | 예 |
|---|---|---|
name |
객체 멤버 접근 | people |
a.b |
중첩 멤버 접근 | store.bicycle.color |
[0] |
배열 인덱스 접근 | people[0] |
[-1] |
마지막 요소 접근 | people[-1] |
[0:2] |
슬라이스, 시작 포함 끝 배타 | people[0:2] |
[*] |
목록 프로젝션 | people[*].name |
[] |
평탄 프로젝션 | people[].tags[] |
[?condition] |
필터 프로젝션 | people[?age > 18] |
{key: expr} |
멀티선택 해시 | {name: name, age: age} |
[expr, expr] |
멀티선택 목록 | [name, age] |
| |
왼쪽 결과를 오른쪽으로 파이프 | people[*].age | [0] |
최소 예:
{
"people": [
{ "name": "Alice", "age": 31, "city": "Shanghai" },
{ "name": "Bob", "age": 17, "city": "Beijing" }
]
}
people[0].name → "Alice"
people[-1].city → "Beijing"
people[0:2].name → ["Alice", "Bob"]
people[0:2].name이 배열을 반환하는 이유에 주목하세요. 슬라이스는 목록을 반환하고, 뒤의 .name이 그 목록의 모든 요소에 자동으로 프로젝션됩니다. JMESPath와 일반 객체 접근의 가장 큰 차이입니다.
3. 프로젝션: 하나의 목록을 다른 목록으로
프로젝션은 JMESPath의 핵심 개념입니다. 두 단계로 동작합니다. 목록의 모든 요소를 평가한 뒤 결과를 새 목록으로 수집합니다. null로 평가되는 요소는 자동으로 버려집니다.
people[*].name // ["Alice", "Bob"]
people[*].{name: name, age: age} // a list of objects
people[?age > 18].name // ["Alice"]
두 번째 줄은 전형적인 프로젝션 표현식입니다. people[*].{name: name, age: age}. 모든 요소를 { "name": ..., "age": ... }로 재구성하고 여분의 city 필드를 버립니다. 노드를 주고 코드에서 매핑하게 하는 JSONPath와 비교하면 루프 하나를 아낍니다.
구분할 만한 두 종류의 프로젝션:
[*]는 원래 목록 구조를 보존하고 null 값을 건너뜁니다.[]는 평탄 프로젝션으로 중첩 목록 한 단계를 먼저 접습니다. "모든 사람의 모든 태그"가 필요한people[].tags[]같은 표현식에 어울립니다.
4. 파이프와 멀티선택
파이프 |는 "현재 프로젝션을 멈추고 결과를 새 입력으로 취급"합니다. 가장 흔한 용도는 프로젝션 뒤에 인덱스를 취하는 것입니다:
people[*].name | [0] → "Alice"
people[*].age | sort(@) | [-1] → the maximum age
파이프가 없으면 people[*].name[0]이 모든 이름의 첫 문자를 취하는 것으로 해석됩니다. 원하는 것이 아닙니다. 파이프는 JMESPath에서 가장 쉽게 간과되고 가장 유용한 기호입니다.
멀티선택에는 두 형태가 있습니다:
// Multi-select hash: the result is an object
people[0].{name: name, age: age}
// Multi-select list: the result is an array
people[0].[name, age]
멀티선택 해시는 이름 있는 결과를 구성하는 데, 멀티선택 목록은 위치로 소비하는 데 좋습니다. 둘 다 프로젝션 안에 중첩할 수 있지만 주의하세요. 멀티선택 해시에서는 값이 null인 키도 유지되지만, 프로젝션은 null 요소 전체를 버립니다.
5. 내장 함수
JMESPath 표준은 함수 집합을 정의합니다. 흔한 것:
| 함수 | 목적 | 예 |
|---|---|---|
length() |
길이 얻기 | length(people) |
sort() |
값 정렬 | sort(people[*].age) |
sort_by() |
필드로 정렬 | sort_by(people, &age) |
max() / min() |
최대 / 최소 | max(people[*].age) |
sum() |
값 합계 | sum(people[*].age) |
keys() |
객체 키 얻기 | keys(people[0]) |
contains() |
포함 여부 | contains(name, "Ali") |
to_string() |
문자열로 변환 | to_string(age) |
sort_by의 두 번째 인자는 & 표현식이며 "각 요소에서 이 필드를 취함"을 뜻합니다:
sort_by(people, &age)[*].name
이 함수들이 JMESPath에 간단한 집계를 가능하게 합니다. JSONPath는 오직 선택만을 위해 설계되어 합계와 그룹화에는 대개 jq나 코드가 필요합니다.
6. JSONPath와 나란히 비교
| 차원 | JSONPath | JMESPath |
|---|---|---|
| 주 목표 | 노드 선택 | 데이터 변환 |
| 루트 기호 | $ |
없음, 필드 이름을 직접 씀 |
| 현재 노드 | @ |
@(함수 인자에서만) |
| 필터 | [?(@.age > 18)] |
[?age > 18] |
| 와일드카드 | [*] |
[*] 또는 [] |
| 새 객체 구성 | 미지원 | {name: name} |
| 파이프 | 미지원 | | |
| 표준 | RFC 9535 | JMESPath 명세 |
한 문장 결정 가이드:
- 조건에 맞는 노드만 찾으면 JSONPath
- 결과를 프런트엔드가 원하는 모양으로 재구성하려면 JMESPath
- 아는 필드 하나를 읽는 것이면 어느 것도 쓰지 말고 속성에 직접 접근
쿼리 결과를 문서에 다시 써야 한다면 두 언어 모두 부적합합니다. JSON Pointer와 패치를 쓰세요. JSON Pointer, Patch, Merge Patch를 참고하세요.
7. AWS CLI의 JMESPath
AWS CLI의 --query 파라미터는 JMESPath이며, 가장 널리 퍼진 사용 사례입니다:
aws ec2 describe-instances \
--query 'Reservations[].Instances[].{Id: InstanceId, State: State.Name, Type: InstanceType}' \
--output table
이 명령은 세 가지를 합니다. []로 두 중첩 목록을 평탄화하고, 멀티선택 해시로 세 필드만 유지하며, --output table로 결과를 표로 렌더합니다. JSONPath로는 계층이 더 필요하고 "이름 변경 + 트리밍" 효과를 얻지 못합니다.
다른 흔한 형태:
# Only running instances
aws ec2 describe-instances --query 'Reservations[].Instances[?State.Name==`running`].[InstanceId,PublicIpAddress]'
# The value of the Name tag
aws ec2 describe-instances --query 'Reservations[].Instances[].Tags[?Key==`Name`].Value[]'
백틱은 문자열이 아니라 리터럴을 감쌉니다. AWS CLI에서 문자열은 단일 인용부호를, JSON 리터럴은 백틱을 씁니다. 이 차이는 초보자가 가장 흔히 하는 실수 중 하나입니다.
8. 흔한 실수
[*]와[]혼동: 전자는 중첩을 보존하고 후자는 한 단계를 평탄화하며, 결과 구조가 완전히 다릅니다.- 파이프 잊기:
people[*].name[0]은 첫 이름이 아니라 각 이름의 첫 문자를 취합니다. - 필터 안에
@쓰기: JMESPath 필터는 필드 이름을 직접 쓰므로[?@.age > 18]은 틀립니다. null을 빈 문자열로 취급: 프로젝션은null을 버리지만 멀티선택 해시는 키를 유지합니다.- AWS CLI 리터럴에 큰따옴표 쓰기: 셸이 먼저 확장해 표현식을 재작성합니다.
디버깅 조언: 실제 데이터에 적용하기 전 최소 입력에서 표현식을 검증하고, AWS CLI에서 --output json을 더해 원시 결과를 보세요. 일상적인 JSON 편집과 형식화에는 JSON 에디터와 명령행 도구와 함께 쓰면 필드 경로 찾기가 훨씬 빨라집니다.
FAQ
JMESPath와 JSONPath를 함께 쓸 수 있나?
예, 하지만 대개 불필요합니다. 흔한 접근은 JMESPath로 작은 객체를 트리밍한 뒤 코드에서 일반 속성 접근을 쓰는 것입니다. 경로로 찾은 뒤 수정하는 것이 정말 필요하면 JSONPath가 읽고 JSON Pointer가 쓰는 분업이 더 명확합니다.
people[*].name이 왜 단일 문자열이 아닌 것을 반환하나?
[*]가 프로젝션이고 결과는 사람이 하나뿐이어도 항상 목록이기 때문입니다. 단일 값을 얻으려면 파이프와 인덱스를 더하세요. people[*].name | [0].
프로젝션 안의 null은 어떻게 되나?
목록 프로젝션은 null로 평가되는 요소를 자동으로 건너뛰므로 결과가 원래 목록보다 짧을 수 있습니다. 멀티선택 해시는 키를 유지하고 null 값을 보존하며, API 응답을 구성할 때 중요합니다.
JMESPath로 집계와 그룹화가 되나?
sum(), max(), sort_by()로 기본 집계는 됩니다. 그룹화, 조인, 윈도 함수는 표준 밖입니다. 복잡한 분석은 jq, SQL, 또는 코드에 맡기세요.
JMESPath와 JSONPath 중 어떻게 결정하나?
경로와 조건으로 노드를 선택해 노드 집합을 받으면 JSONPath, 결과를 프런트엔드가 원하는 모양으로 필드 이름 변경과 트리밍까지 해 재구성하면 JMESPath를 쓰세요. 아는 필드 하나를 읽는 것이면 어느 것도 쓰지 말고 속성에 접근하세요.
프로젝션의 첫 값을 얻으려면 파이프를 써야 하나?
예. people[*].name은 프로젝션이라 결과가 항상 목록이고, 파이프가 없으면 people[*].name[0]이 모든 이름의 첫 문자를 취합니다. people[*].name | [0]으로 첫 값을 얻으세요.