Bahasa Query JSONPath dalam Praktik
Intinya: Pada 2024 JSONPath akhirnya mendapatkan standar formal, RFC 9535. Artikel ini menjelaskan sintaks, semantik filter, fungsi, dan perbedaan historis antar implementasi.

Intinya: lokasikan dengan path eksplisit terlebih dahulu dan filter di atasnya, serta tulis ekspresi sesuai RFC 9535; gunakan akses properti atau JSON Pointer untuk satu field tetap, dan jq atau kode untuk agregasi.
Anda mungkin pernah melihat ekspresi seperti
$.store.book[?(@.price < 10)].title, tetapi ia baru menjadi standar internasional pada 2024.
1. Dari Standar De Facto menjadi Standar Formal
JSONPath diusulkan oleh Stefan Gössner pada 2007, dan untuk waktu lama satu-satunya "spesifikasi" adalah satu posting blog, dengan setiap implementasi bahasa melakukan caranya sendiri:
- apakah filter memerlukan tanda kurung (
?(@.a)vs?@.a); - apakah indeks array
[-1]didukung; - apakah hasil mengembalikan "nilai" atau "nilai dan path";
- apakah ekspresi skrip (
?(@.price < $.max)) diizinkan.
RFC 9535, yang diterbitkan pada Februari 2024, menyatukan divergensi ini menjadi satu sintaks dan semantik dan menyediakan compliance test suite resmi. Proyek baru harus mengikuti RFC 9535; saat memigrasi proyek lama, pastikan dulu versi implementasinya.
2. Contekan Sintaks
| Sintaks | Arti | Contoh |
|---|---|---|
$ |
Node akar | $.store |
@ |
Node saat ini di dalam filter | $[?@.price < 10] |
.name |
Node anak (nama harus cocok dengan [A-Za-z_][A-Za-z0-9_]*) |
$.store.book |
['name'] |
Node anak (nama apa pun, termasuk non-ASCII dan titik) | $['store']['书'] |
[0] |
Indeks array; angka negatif dihitung dari akhir | $.book[0], $.book[-1] |
[0,2] |
Beberapa indeks / beberapa nama | $.book[0,2] |
[1:3] |
Slice, awal inklusif dan akhir eksklusif | $.book[1:3] |
[::2] |
Slice dengan langkah | $.book[::2] |
[*] |
Wildcard untuk semua node anak | $.book[*].title |
..name |
Recursive descent, mencari pada kedalaman apa pun | $..price |
..[*] |
Secara rekursif mengambil semua node | $..* |
[?<expr>] |
Selektor filter | $.book[?@.price < 10] |
Satu semantik yang harus Anda ingat: JSONPath mengembalikan himpunan node (nodelist), bukan satu nilai. Bahkan ketika hanya satu node yang cocok, hasilnya adalah list dengan panjang satu.
3. Menelusuri Sintaks dengan Satu Contoh
{
"store": {
"book": [
{ "title": "JSON 权威指南", "price": 8.95, "tags": ["json", "spec"] },
{ "title": "数据交换格式史", "price": 12.99, "tags": ["history"] },
{ "title": "解析器实现原理", "price": 8.99, "tags": ["parser", "json"] }
],
"bicycle": { "color": "red", "price": 19.95 }
},
"expensive": 10
}
| Ekspresi | Hasil |
|---|---|
$.store.bicycle.color |
["red"] |
$..price |
4 harga (termasuk harga sepeda) |
$.store.book[*].title |
3 judul |
$.store.book[?@.price < 10].title |
Judul buku 1 dan 3 |
$.store.book[?@.tags] |
Semua buku yang memiliki tags (uji keberadaan) |
$.store.book[-1].title |
Judul buku terakhir |
$..book[0,1] |
Dua buku pertama |
$.store.book[?@.price < $.expensive] |
Secara sintaks bergantung implementasi; RFC 9535 tidak mengizinkan referensi lintas akar |
Baris terakhir adalah jebakan historis: banyak implementasi mengizinkan $.expensive di dalam filter untuk perbandingan lintas node, yang secara eksplisit dilarang RFC 9535 —
filter hanya dapat mengakses node saat ini @. Ketika Anda perlu membandingkan dengan ambang global, lewatkan ambang sebagai argumen ke fungsi query.
4. Apa yang Dapat Diekspresikan Filter
Ekspresi filter RFC 9535 mendukung empat jenis konstruksi:
Logic: && || ! e.g. ?@.price < 10 && @.tags
Comparison: == != < <= > >=
Existence: a bare path e.g. ?@.tags (tests existence)
Functions: length() count() match() search() value()
Beberapa contoh praktis:
// Harga antara 8 dan 10
$.store.book[?@.price >= 8 && @.price <= 10]
// Judul mengandung "JSON" (pencocokan regex; argumen kedua adalah I-Regexp)
$.store.book[?match(@.title, ".*JSON.*")]
// Lebih dari satu tag
$.store.book[?length(@.tags) > 1]
// Ambil nilai cocok pertama alih-alih list (value() mengubah himpunan node tunggal menjadi nilai)
$.store.book[?@.price < 9].title.value()
match() adalah pencocokan seluruh string dan search() adalah pencarian substring; dialek regex yang mendasari adalah I-Regexp (subset dari RFC 9485),
yang tidak mendukung backreference maupun lookaround — itu sekaligus keterbatasan dan perisai yang berguna terhadap regex DoS.
5. Perbedaan Implementasi: Periksa Sebelum Migrasi
| Implementasi | Bahasa | Dukungan RFC 9535 | Catatan |
|---|---|---|---|
jsonpath-plus |
JavaScript | Sebagian besar kompatibel | Mendukung fungsi ekstensi dan ekspresi skrip; lebih mampu tetapi tidak sepenuhnya standar |
jsonpath (dchester) |
JavaScript | Parsial | Library lama dengan sintaks ?() dan struktur hasil berbeda |
ojg / gjson |
Go | ojg memiliki dukungan baik |
gjson menggunakan sintaks path lain dan bukan JSONPath |
jsonpath-ng |
Python | Parsial | Mendukung ekstensi dan cukup permisif |
Jayway JsonPath |
Java | Parsial | Ekosistem luas; perilaku historis berbeda dari RFC |
Tiga pemeriksaan sebelum migrasi:
- Apakah tanda kurung filter wajib;
- Apakah indeks negatif seperti
-1didukung; - Apakah hasilnya "array nilai" atau "array objek
{path, value}".
Yang ketiga paling mudah tersandung: frontend membaca result[0], dan setelah berganti library ia menerima objek, membuat halaman kosong.
6. Performa dan Keamanan
Performa
- JSONPath pada dasarnya adalah satu traversal dengan kompleksitas O(jumlah node), dan ia tidak dapat menggunakan indeks;
- Recursive descent dengan
..adalah yang paling mahal; jika Anda dapat melokasikan dengan$.store.bookdulu lalu memfilter, jangan menulis$..book; - Kompilasi ekspresi sekali dan gunakan kembali (sebagian besar library menawarkan
compileatau caching) alih-alih mengurai ulang pada setiap query; - Untuk dokumen dengan lebih dari 100.000 node, pertimbangkan ekstraksi tepat dengan JSON Pointer terlebih dahulu, atau prapemrosesan di server dengan
jq.
Keamanan
- Ekspresi dari masukan pengguna harus memiliki batas panjang dan kedalaman penyarangan;
- Jangan biarkan JSONPath menangani otorisasi — ia hanya dapat membaca, dan tidak dapat mengekspresikan "field mana yang tidak terlihat";
- Ketika regex yang digunakan
match()berasal dari pengguna, batasi kompleksitasnya; meskipun lebih aman daripada PCRE, batas panjang tetap disarankan.
7. Kapan Tidak Menggunakan JSONPath
- Anda hanya membutuhkan satu field tetap → gunakan
data.store.bicycle.colorsecara langsung; - Anda perlu menulis balik setelah memodifikasi → gunakan JSON Pointer atau patch; JSONPath tidak menulis balik;
- Anda memerlukan agregasi (penjumlahan, pengelompokan) → gunakan
jq, SQL, atau kode; JSONPath hanya memilih; - Dokumennya sangat besar dan Anda hanya membutuhkan beberapa field → gunakan parser streaming alih-alih membaca seluruh pohon ke memori terlebih dahulu.
8. Daftar Periksa
- [ ] Ekspresi mengikuti RFC 9535 dan tidak bergantung pada referensi lintas akar;
- [ ] Jelas bahwa hasilnya adalah himpunan node; gunakan
[0]atauvalue()untuk mendapatkan satu nilai; - [ ] Ekspresi dikompilasi terlebih dahulu dan digunakan kembali;
- [ ] Ekspresi yang dikendalikan pengguna memiliki batas panjang dan kompleksitas;
- [ ] Kasus uji compliance resmi dijalankan saat berganti implementasi.
FAQ
Bagaimana saya memilih antara JSON Pointer dan JSONPath?
Gunakan akses properti biasa atau JSON Pointer ketika Anda hanya membutuhkan satu field tetap; gunakan JSONPath ketika Anda memerlukan filter, wildcard, atau recursive descent. JSONPath hanya memilih dan tidak pernah menulis balik, jadi penulisan balik milik JSON Pointer atau patch.
Mengapa filter tidak dapat merujuk $.expensive?
RFC 9535 secara eksplisit melarang referensi lintas akar; filter hanya boleh mengakses node saat ini @. Lewatkan ambang sebagai argumen ke fungsi query sebagai gantinya. Implementasi lama menerima sintaks ini, tetapi ia rusak ketika Anda berganti atau meningkatkan library.
Haruskah saya mengandalkan result[0] untuk membaca nilai?
Hanya setelah memastikan bahwa implementasi saat ini mengembalikan array nilai. Beberapa library mengembalikan objek {path, value}, sehingga result[0] menjadi objek dan halaman menjadi kosong. Baca nilai tunggal melalui [0] atau value(), dan jalankan compliance suite resmi sebelum berganti implementasi.