JSONPath 테스터
RFC 9535 JSONPath 쿼리를 JSON에 대해 테스트합니다. 선택자, 필터, 다섯 가지 함수, 그리고 각 일치의 정규화 경로 — 모두 브라우저에서.
- 경로
$['store']['book'][0]['title']"Sayings of the Century" - 경로
$['store']['book'][2]['title']"Moby Dick"
마침내 표준이 생긴 쿼리 언어
JSONPath는 JSON에게 XPath가 XML에게 하는 것과 같습니다. 문서의 일부를 가리키는 작은 언어입니다. $.store.book[0].title 같은 식을 쓰면 일치하는 노드를 선택합니다. 이 아이디어는 2007년 Stefan Goessner의 블로그 글에서 왔고, 십칠 년 동안 그 글이 유일한 참조였습니다 — 즉 모든 라이브러리가 빈틈을 저마다 다르게 채웠다는 뜻입니다. 단독 $..는 무엇을 선택하나요? $[1,2]는 순서대로 돌아온다고 보장되나요? 필터는 없는 값을 어떻게 비교하나요? 세 개의 JSONPath 라이브러리에 물으면 세 가지 답을 얻을 수도 있습니다.
2024년에 발행된 RFC 9535가 마침내 그 모두를 확정했습니다. 이 테스터는 민담이 아니라 그 표준을 겨냥합니다. RFC가 말하는 대로 쿼리를 실행하고, 각 일치를 정확한 위치와 함께 보여 주며, 그리고 — 중요하게 — 오래된 방언에서는 유효하지만 RFC 9535에서는 무효한 쿼리를 거부하고, 다른 도구라면 다르게 했을 비표준 동작을 조용히 하는 대신 문제가 어디인지 알려 줍니다.
세그먼트와 선택자
쿼리는 루트 식별자 $ 뒤에 세그먼트의 나열이 오는 것이며, 각 세그먼트는 받은 노드에 하나 이상의 선택자를 적용합니다. 선택자는 다섯 가지입니다.
- 이름 — $.store 또는 $["store"]는 멤버의 값을 선택합니다. 점 형태는 약식이고, 대괄호와 따옴표 형태는 공백이나 문장부호가 있는 것을 포함해 어떤 키에도 통합니다.
- 와일드카드 — *는 객체의 모든 멤버 또는 배열의 모든 요소를 선택합니다.
- 인덱스 — [0]은 배열 요소를 선택하고, 음수 인덱스는 끝에서부터 세므로 [-1]이 마지막입니다.
- 슬라이스 — [start:end:step]은 범위를 선택합니다. Python과 똑같이 [1:3]은 요소 1과 2, [::-1]은 뒤집기, [::2]는 하나 걸러 하나입니다.
- 필터 — [?<식>]은 식이 참인 요소나 멤버만 남깁니다(아래 참조).
대괄호는 한 번에 여러 선택자를 가질 수 있습니다. [0, 2, "title"]은 한 세그먼트에서 세 가지를 선택합니다. 그리고 세그먼트는 자식 세그먼트(단일 점 또는 대괄호)이거나 자손 세그먼트(..)일 수 있으며, 후자는 노드와 그 모든 자손을 검색합니다 — $..author는 문서 어디에 있는 author든 찾습니다.
필터, 그리고 없음을 비교하는 네 가지 방법
필터 선택자는 각 요소를 논리식으로 검사하며, 그 안에서 @는 현재 요소를, $는 문서 전체를 가리킵니다. 식은 값을 비교하고(==, !=, <, <=, >, >=), &&와 || 그리고 !로 검사를 결합하며, 단순히 존재를 검사할 수 있습니다. $..book[[email protected]]은 ISBN이 있는 책을 남기는데, @.isbn은 키가 있을 때만 노드를 선택하기 때문입니다.
미묘한 부분은 없는 것을 비교하는 것입니다. 비교의 왼쪽이나 오른쪽의 쿼리는 값 또는 없음을 냅니다. RFC는 이를 정확히 정의합니다. 없음은 없음과 같고, 없음은 어떤 실제 값과도 같지 않으며, 없음에 대한 모든 순서 검사(<, >)는 그냥 거짓입니다. 그래서 @.price < 10은 price가 없는 요소를 오류 대신 조용히 건너뜁니다 — 대개 원하는 바이고, 언제나 표준이 말하는 바입니다.
다섯 가지 함수
RFC 9535는 필터 안에서 호출할 수 있는 다섯 가지 함수를 추가합니다.
- length() — 문자열(문자 수로 셈), 배열 또는 객체의 길이.
- count() — 쿼리가 선택하는 노드 수. 개수로 필터할 수 있습니다. [?count(@.chapters) > 3].
- value() — 쿼리가 선택하는 단일 값, 또는 영 개나 여러 개를 선택하면 없음.
- match() — 문자열이 정규식과 완전히 일치하는지.
- search() — 정규식이 문자열 어딘가에서 발견되는지.
표준은 이들의 사용 방식에 엄격하며, 이 테스터는 파싱 시 그것을 강제합니다. length()는 정확히 하나의 값을 받으므로 length(@.*) — 많은 노드를 선택할 수 있는 쿼리 — 는 조용히 오작동하는 쿼리가 아니라 구문 오류입니다. 마찬가지로 match()는 참 또는 거짓을 반환하므로 match(@.a, "x") == true라고 쓰면 거부됩니다. 논리 결과는 비교하는 것이 아니기 때문입니다.
정규식은 I-Regexp입니다
match()와 search()는 JavaScript 정규식을 쓰지 않습니다. I-Regexp(RFC 9485)를 씁니다. 이는 모든 언어에서 똑같이 동작하도록 설계된 작고 이식 가능한 부분집합입니다. 대부분은 예상대로입니다 — 문자 클래스, 수량자, 교대, 대문자를 뜻하는 \p{Lu} 같은 Unicode 속성. 유일한 함정은 점입니다. I-Regexp에서 .은 캐리지 리턴이나 라인 피드를 제외한 모든 문자에 일치합니다. 즉 JavaScript의 점이 제외하는 Unicode 줄 구분자 U+2028과 U+2029에는 일치합니다. 이 테스터는 I-Regexp를 충실히 컴파일하므로, 패턴은 준수하는 서버가 평가하는 것과 똑같이 여기서 동작합니다.
두 함수의 차이는 앵커링뿐입니다. match는 문자열 전체가 일치할 것을 요구하고, search는 그 안 어딘가에서 패턴을 찾습니다. match(@, "a.*")는 "abc"를 받아들이고, search(@, "b")는 b를 포함하는 어떤 문자열이든 받아들입니다.
정규화 경로, 그리고 브라우저에서의 실행
각 일치에 대해 이 테스터는 Normalized Path를 표시합니다 — RFC가 정의하는 정준적 위치로, 대괄호와 따옴표 형식으로 작성됩니다. $['store']['book'][0]['author']. 많은 노드를 선택할 수 있는 쿼리와 달리, 정규화 경로는 이름과 인덱스 선택자만과 고정된 따옴표 스타일로 정확히 하나를 가리킵니다. 이는 "이 일치가 어디서 왔는가"에 대한 답이며, 와일드카드 결과를 구체적인 위치의 집합으로 되돌릴 수 있게 해 줍니다. 대부분의 테스터는 값을 보여 주고 경로 계산은 여러분에게 맡기지만, 이것은 둘 다 보여 줍니다.
엔진 전체가 여러분의 브라우저에서 실행됩니다 — JSON은 파싱되고 쿼리는 여러분의 기기에서 평가되며, 붙여넣은 것은 무엇도 업로드되거나 저장되거나 기록되지 않습니다. 공식 JSONPath Compliance Test Suite로 검증되어, 그 답은 한 라이브러리의 해석이 아니라 표준과 일치합니다.
자주 묻는 질문
- 이것은 어떤 JSONPath 방언을 쓰나요?
- RFC 9535, 2024년 IETF 표준이며, 공식 JSONPath Compliance Test Suite로 검증됩니다. RFC에서 벗어나는 곳에서 오래된 Goessner 관례를 의도적으로 받아들이지 않습니다. 그런 쿼리는 고칠 수 있도록 문제의 위치와 함께 거부됩니다.
- 다른 도구에서는 되는데 왜 제 쿼리가 거부되었나요?
- 그 도구가 표준 이전의 Goessner 관례를 따르기 때문이며, 그것은 RFC 9535와 여러 곳에서 다릅니다 — 단독 $.., [(@.length-1)] 같은 스크립트 식, 선행 영이 있는 인덱스, 특수문자가 있는 따옴표 없는 이름은 모두 비표준입니다. RFC는 이들을 잘 정의된 대응물로 대체했고, 이 테스터는 RFC를 따릅니다.
- 정규화 경로란 무엇인가요?
- 단일 노드의 정준적 위치로, RFC가 정의하는 대괄호와 따옴표 형식으로 작성됩니다. 예를 들어 $['store']['book'][0]['author']. 쿼리는 많은 노드에 일치할 수 있지만, 각 일치에는 정확히 하나의 정규화 경로가 있으므로 테스터는 이를 모든 값 옆에 표시합니다.
- 필터는 없는 값을 어떻게 다루나요?
- 없음으로, RFC가 정하는 규칙으로 다룹니다. 없음은 없음과 같고, 없음은 어떤 실제 값과도 같지 않으며, 없음을 포함하는 모든 순서 비교(<, >)는 거짓입니다. 그래서 @.price < 10은 price가 없는 요소를 오류를 던지는 대신 그냥 건너뜁니다.
- match()와 search()는 JavaScript 정규식을 쓰나요?
- 아니요. I-Regexp(RFC 9485)라는 이식 가능한 부분집합을 씁니다. 주된 실질적 차이는 점으로, 캐리지 리턴과 라인 피드를 제외한 모든 것에 일치합니다 — JavaScript의 점이 제외하는 U+2028과 U+2029를 포함합니다. 이 테스터는 I-Regexp를 충실히 컴파일하여 결과가 준수 구현과 일치하도록 합니다.
- match와 search의 차이는 무엇인가요?
- 앵커링입니다. match()는 마치 앵커로 둘러싸인 것처럼 문자열 전체가 패턴과 일치할 것을 요구하고, search()는 패턴이 문자열 어딘가에서 발견되면 성공합니다. 나머지는 모두 동일합니다.
- 제 JSON이 서버로 전송되나요?
- 아니요. 문서는 파싱되고 쿼리는 전적으로 브라우저에서 평가되며, 붙여넣은 것은 무엇도 업로드되거나 기록되지 않습니다. 실제 API 응답이나 설정 파일에 대해 안전하게 테스트할 수 있습니다.