Penguji JSONPath

Uji kueri JSONPath (RFC 9535) terhadap JSON: selektor, filter, kelima fungsi, dan jalur ternormalisasi setiap kecocokan — semua di browser Anda.

Kueri JSONPath
JSON
Kecocokan: 2
  • Jalur$['store']['book'][0]['title']
    "Sayings of the Century"
  • Jalur$['store']['book'][2]['title']
    "Moby Dick"

Bahasa kueri yang akhirnya punya standar

JSONPath bagi JSON adalah seperti XPath bagi XML: bahasa kecil untuk menunjuk bagian sebuah dokumen. Anda menulis ekspresi seperti $.store.book[0].title dan ia memilih simpul yang cocok. Idenya berasal dari tulisan blog Stefan Goessner tahun 2007, dan selama tujuh belas tahun tulisan itu satu-satunya rujukan — yang berarti setiap pustaka mengisi celahnya secara berbeda. Apa yang dipilih $.. sendirian? Apakah $[1,2] dijamin kembali berurutan? Bagaimana sebuah filter membandingkan nilai yang tidak ada? Tanyakan pada tiga pustaka JSONPath dan Anda bisa mendapat tiga jawaban.

RFC 9535, terbit tahun 2024, akhirnya memakukan semuanya. Penguji ini menyasar standar itu, bukan cerita rakyat. Ia menjalankan kueri seperti yang dikatakan RFC, menunjukkan setiap kecocokan beserta lokasi persisnya, dan — yang penting — menolak kueri yang valid di dialek lama tetapi tidak di RFC 9535, memberi tahu Anda di mana masalahnya alih-alih diam-diam melakukan sesuatu yang tidak standar yang akan dilakukan alat lain secara berbeda.

Segmen dan selektor

Sebuah kueri adalah pengenal akar $ diikuti barisan segmen, dan setiap segmen menerapkan satu atau lebih selektor pada simpul yang diterimanya. Ada lima selektor:

  • Nama — $.store atau $["store"] memilih nilai sebuah anggota. Bentuk titik adalah singkatan; bentuk kurung siku dan tanda kutip bekerja untuk kunci apa pun, termasuk yang berisi spasi atau tanda baca.
  • Wildcard — * memilih setiap anggota objek atau setiap elemen array.
  • Indeks — [0] memilih sebuah elemen array, dan indeks negatif menghitung dari belakang, jadi [-1] adalah yang terakhir.
  • Irisan (slice) — [start:end:step] memilih sebuah rentang, persis seperti di Python: [1:3] adalah elemen 1 dan 2, [::-1] membalik, [::2] mengambil satu tiap dua.
  • Filter — [?<ekspresi>] hanya menyimpan elemen atau anggota yang ekspresinya benar (dijelaskan di bawah).

Sebuah kurung dapat memuat beberapa selektor sekaligus: [0, 2, "title"] memilih tiga hal dalam satu segmen. Dan sebuah segmen bisa berupa segmen anak (satu titik atau kurung tunggal) atau segmen keturunan (..), yang mencari di simpul dan semua keturunannya — $..author menemukan setiap author di mana pun dalam dokumen.

Filter, dan empat cara membandingkan ketiadaan

Sebuah selektor filter menguji setiap elemen dengan ekspresi logis, yang di dalamnya @ merujuk ke elemen saat ini dan $ ke seluruh dokumen. Ekspresi dapat membandingkan nilai (==, !=, <, <=, >, >=), menggabungkan uji dengan && dan || dan !, serta sekadar menguji keberadaan: $..book[[email protected]] menyimpan buku yang punya ISBN, karena @.isbn memilih simpul hanya ketika kuncinya ada.

Bagian halusnya adalah membandingkan sesuatu yang tidak ada. Kueri di kiri atau kanan sebuah perbandingan menghasilkan sebuah nilai atau ketiadaan. RFC mendefinisikannya dengan tepat: ketiadaan sama dengan ketiadaan, ketiadaan tidak sama dengan nilai nyata apa pun, dan uji urutan apa pun (<, >) terhadap ketiadaan sekadar salah. Jadi @.price < 10 diam-diam melewati elemen yang tidak punya price alih-alih menghasilkan galat — yang biasanya Anda inginkan, dan selalu yang dikatakan standar.

Lima fungsi

RFC 9535 menambahkan lima fungsi yang bisa Anda panggil di dalam sebuah filter:

  • length() — panjang sebuah string (dihitung dalam karakter), array, atau objek.
  • count() — berapa banyak simpul yang dipilih sebuah kueri, sehingga Anda bisa memfilter berdasarkan kardinalitas: [?count(@.chapters) > 3].
  • value() — satu-satunya nilai yang dipilih sebuah kueri, atau ketiadaan jika ia memilih nol atau banyak.
  • match() — apakah sebuah string cocok dengan ekspresi reguler secara penuh.
  • search() — apakah ekspresi reguler ditemukan di mana pun dalam sebuah string.

Standar ketat soal bagaimana ini digunakan, dan penguji ini memberlakukannya saat penguraian: length() mengambil tepat satu nilai, jadi length(@.*) — kueri yang bisa memilih banyak simpul — adalah galat sintaksis, bukan kueri yang diam-diam salah berperilaku. Demikian pula match() mengembalikan benar atau salah, jadi menulis match(@.a, "x") == true ditolak, karena hasil logis bukanlah sesuatu yang dibandingkan.

Ekspresi regulernya adalah I-Regexp

match() dan search() tidak memakai ekspresi reguler JavaScript; keduanya memakai I-Regexp (RFC 9485), sebuah subset kecil dan portabel yang dirancang untuk berperilaku sama di semua bahasa. Sebagian besarnya seperti yang Anda duga — kelas karakter, kuantifier, alternasi, properti Unicode seperti \p{Lu} untuk huruf kapital. Satu-satunya jebakan adalah titik: di I-Regexp, . cocok dengan karakter apa pun kecuali carriage return atau line feed, yang berarti ia memang cocok dengan pemisah baris Unicode U+2028 dan U+2029 yang dikecualikan titik JavaScript. Penguji ini mengompilasi I-Regexp dengan setia, sehingga sebuah pola berperilaku di sini seperti yang akan dievaluasi server yang patuh.

Perbedaan antara kedua fungsi hanyalah penjangkaran: match menuntut seluruh string cocok, sedangkan search mencari pola di mana pun di dalamnya. match(@, "a.*") menerima "abc"; search(@, "b") menerima string apa pun yang mengandung b.

Jalur ternormalisasi, dan menjalankan di browser

Untuk setiap kecocokan, penguji ini menampilkan sebuah Normalized Path — lokasi kanonik yang didefinisikan RFC, ditulis dalam bentuk kurung siku dan tanda kutip: $['store']['book'][0]['author']. Berbeda dari kueri, yang bisa memilih banyak simpul, jalur ternormalisasi menunjuk tepat satu, hanya memakai selektor nama dan indeks serta gaya kutip tetap. Ia adalah jawaban atas "dari mana kecocokan ini berasal", dan itulah yang memungkinkan Anda mengubah hasil wildcard kembali menjadi himpunan lokasi konkret. Kebanyakan penguji menampilkan nilai dan membiarkan Anda menyimpulkan jalurnya; yang ini menampilkan keduanya.

Seluruh mesin berjalan di browser Anda — JSON diurai dan kueri dievaluasi di perangkat Anda, dan tidak ada yang Anda tempel diunggah, disimpan, atau dicatat. Ia divalidasi terhadap JSONPath Compliance Test Suite resmi, sehingga jawabannya sesuai dengan standar, bukan dengan tafsir satu pustaka.

Pertanyaan yang sering diajukan

Dialek JSONPath mana yang dipakai ini?
RFC 9535, standar IETF 2024, dan divalidasi terhadap JSONPath Compliance Test Suite resmi. Ia sengaja tidak menerima konvensi Goessner lama di mana mereka menyimpang dari RFC; kueri semacam itu ditolak dengan posisi masalahnya agar bisa Anda perbaiki.
Mengapa kueri saya ditolak padahal bekerja di alat lain?
Karena alat itu mengikuti konvensi Goessner pra-standar, yang berbeda dari RFC 9535 di beberapa tempat — $.. sendirian, ekspresi skrip seperti [(@.length-1)], indeks berangka nol di depan, dan nama tanpa kutip dengan karakter khusus semuanya tidak standar. RFC menggantinya dengan padanan yang terdefinisi baik, dan penguji ini berpegang pada RFC.
Apa itu jalur ternormalisasi?
Lokasi kanonik satu simpul, ditulis dalam bentuk kurung siku dan tanda kutip yang didefinisikan RFC, mis. $['store']['book'][0]['author']. Sebuah kueri bisa cocok dengan banyak simpul; setiap kecocokan punya tepat satu jalur ternormalisasi, itulah sebabnya penguji menampilkannya di samping setiap nilai.
Bagaimana filter memperlakukan nilai yang hilang?
Sebagai ketiadaan, dengan aturan yang dipakukan RFC: ketiadaan sama dengan ketiadaan, ketiadaan tidak sama dengan nilai nyata apa pun, dan perbandingan urutan apa pun (<, >) yang melibatkan ketiadaan adalah salah. Jadi @.price < 10 sekadar melewati elemen tanpa price alih-alih melempar galat.
Apakah match() dan search() memakai ekspresi reguler JavaScript?
Tidak. Keduanya memakai I-Regexp (RFC 9485), sebuah subset portabel. Perbedaan praktis utamanya adalah titik, yang cocok dengan segalanya kecuali carriage return dan line feed — termasuk U+2028 dan U+2029, yang dikecualikan titik JavaScript. Penguji ini mengompilasi I-Regexp dengan setia agar hasilnya sesuai dengan implementasi yang patuh.
Apa beda match dan search?
Penjangkaran. match() menuntut seluruh string cocok dengan pola, seakan diapit jangkar; search() berhasil jika pola ditemukan di mana pun dalam string. Selebihnya identik.
Apakah JSON saya dikirim ke server?
Tidak. Dokumen diurai dan kueri dievaluasi sepenuhnya di browser Anda, dan tidak ada yang Anda tempel diunggah atau dicatat. Aman diuji terhadap respons API sungguhan atau berkas konfigurasi.