JSONPath란 무엇인가요?
JSONPath는 JSON 문서에서 값을 추출하기 위한 쿼리 언어입니다. 2007년 Stefan Goessner가 XML의 XPath에 대응하는 개념으로 처음 제안했으며, JSONPath를 사용하면 $.store.book[*].author와 같은 표현식으로 반복문이나 수동 탐색 코드 없이 book 배열 안의 모든 author 필드를 선택할 수 있습니다. 이 언어는 2024년 2월 IETF가 발표한 RFC 9535로 표준화되었습니다.
JSONPath 표현식은 항상 문서의 루트를 나타내는 $로 시작합니다. 그 다음 셀렉터를 연결합니다. 객체 키에는 점 표기법, 배열 인덱스에는 대괄호 표기법, 모든 자식 요소에는 와일드카드, 중첩의 모든 레벨을 탐색하려면 재귀 하강 연산자(..)를 사용합니다. [?(@.price < 10)]과 같은 필터 표현식으로 값에 대한 조건을 기반으로 요소를 선택할 수 있습니다.
JSONPath는 Postman과 같은 API 테스트 도구, Datadog 및 Splunk 같은 옵저버빌리티 플랫폼, Apache NiFi 같은 워크플로 엔진, 그리고 JavaScript, Python, Go, Java, C# 전반의 프로그래밍 라이브러리에서 활용됩니다. 코드나 설정 파일에 표현식을 넣기 전에 실제 데이터로 테스트하면 JSON 구조가 예상과 다를 때 발생하는 조용한 오류를 방지할 수 있습니다.
이 JSONPath 테스터를 사용하는 이유
JSONPath 표현식을 직접 작성하면 오류가 발생하기 쉽습니다. 점 하나가 빠지거나 잘못된 대괄호 유형, 또는 잘못된 필터 문법은 아무런 오류 메시지 없이 빈 결과를 반환할 수 있습니다. 이 도구는 표현식이 무엇과 일치하는지 즉각적인 시각적 피드백을 제공합니다.
JSONPath 활용 사례
JSONPath 구문 참조
RFC 9535는 표준 JSONPath 구문을 정의합니다. 아래 표는 대부분의 쿼리에서 사용하게 될 연산자를 다룹니다. 모든 표현식은 $(루트 노드)로 시작하며 하나 이상의 셀렉터를 연결해 문서 구조를 탐색합니다.
| 연산자 | 설명 | 예시 |
|---|---|---|
| $ | Root element | $.store |
| .key | Child property | $.store.book |
| [n] | Array index (zero-based) | $.store.book[0] |
| [*] | All elements in array/object | $.store.book[*] |
| .. | Recursive descent | $..author |
| [start:end] | Array slice | $.store.book[0:2] |
| [?()] | Filter expression | $.store.book[?(@.price<10)] |
| @ | Current node (inside filter) | $.store.book[?(@.isbn)] |
JSONPath vs jq vs XPath
JSONPath, jq, XPath는 동일한 문제(구조화된 데이터 쿼리)를 서로 다른 형식과 사용 사례에서 해결합니다. JSONPath는 JSON을 대상으로 하며 대부분의 언어에서 라이브러리로 제공됩니다. jq는 자체 튜링 완전 필터 언어를 가진 독립 실행형 CLI 도구입니다. XPath는 XML을 대상으로 하며 W3C 사양 스택의 일부입니다.
| 기능 | JSONPath | jq | XPath |
|---|---|---|---|
| 데이터 형식 | JSON | JSON | XML |
| 첫 번째 요소 접근 | $.store.book[0] | .store.book[0] | /store/book[1] |
| 재귀 검색 | $..price | .. | .price? | //price |
| 필터 표현식 | [?(@.price<10)] | select(.price < 10) | [price<10] |
| 사양 | RFC 9535 | stedolan.github.io | W3C XPath 3.1 |
코드 예제
인기 있는 언어와 도구에서 JSONPath 표현식을 평가하는 방법입니다. 각 예제는 비교를 위해 동일한 bookstore JSON 구조를 사용합니다.
import { JSONPath } from 'jsonpath-plus';
const data = {
store: {
book: [
{ title: 'Moby Dick', price: 8.99 },
{ title: 'The Great Gatsby', price: 12.99 }
]
}
};
// Get all book titles
JSONPath({ path: '$.store.book[*].title', json: data });
// → ['Moby Dick', 'The Great Gatsby']
// Recursive descent — find every price in the document
JSONPath({ path: '$..price', json: data });
// → [8.99, 12.99]
// Filter — books under $10
JSONPath({ path: '$.store.book[?(@.price < 10)]', json: data });
// → [{ title: 'Moby Dick', price: 8.99 }]from jsonpath_ng.ext import parse
import json
data = {
"store": {
"book": [
{"title": "Moby Dick", "price": 8.99},
{"title": "The Great Gatsby", "price": 12.99}
]
}
}
# Get all book titles
expr = parse("$.store.book[*].title")
titles = [match.value for match in expr.find(data)]
# → ['Moby Dick', 'The Great Gatsby']
# Recursive descent — all prices
expr = parse("$..price")
prices = [match.value for match in expr.find(data)]
# → [8.99, 12.99]
# Filter — books under $10
expr = parse("$.store.book[?price < 10]")
cheap = [match.value for match in expr.find(data)]
# → [{"title": "Moby Dick", "price": 8.99}]package main
import (
"fmt"
"github.com/ohler55/ojg/jp"
"github.com/ohler55/ojg/oj"
)
func main() {
src := `{
"store": {
"book": [
{"title": "Moby Dick", "price": 8.99},
{"title": "The Great Gatsby", "price": 12.99}
]
}
}`
obj, _ := oj.ParseString(src)
// Get all book titles
expr, _ := jp.ParseString("$.store.book[*].title")
results := expr.Get(obj)
fmt.Println(results)
// → [Moby Dick The Great Gatsby]
// Recursive descent — all prices
expr2, _ := jp.ParseString("$..price")
fmt.Println(expr2.Get(obj))
// → [8.99 12.99]
}# jq uses its own syntax, not JSONPath, but solves the same problem.
# Mapping common JSONPath patterns to jq:
# $.store.book[*].title → get all titles
echo '{"store":{"book":[{"title":"Moby Dick"},{"title":"Gatsby"}]}}' | \
jq '.store.book[].title'
# → "Moby Dick"
# → "Gatsby"
# $..price → recursive descent for "price" keys
echo '{"a":{"price":1},"b":{"price":2}}' | \
jq '.. | .price? // empty'
# → 1
# → 2
# Filter: books where price < 10
echo '{"store":{"book":[{"title":"A","price":8},{"title":"B","price":12}]}}' | \
jq '.store.book[] | select(.price < 10)'
# → {"title":"A","price":8}