JSONPath 테스터

RFC 9535 JSONPath 쿼리를 JSON에 대해 테스트합니다. 선택자, 필터, 다섯 가지 함수, 그리고 각 일치의 정규화 경로 — 모두 브라우저에서.

JSONPath 쿼리
JSON
일치: 2
  • 경로$['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 응답이나 설정 파일에 대해 안전하게 테스트할 수 있습니다.