FAQ Kueri dan Validasi: JSONPath dan Schema
Intinya: Apakah kueri path sebaiknya memakai JSONPath atau Pointer, bagaimana memilih antara Patch dan Merge Patch, apa yang bisa dicegah Schema, dan bagaimana memilih versi draft — semuanya dijelaskan dalam satu kali baca.

Intinya: gunakan JSONPath untuk menemukan node berdasarkan kondisi, JSON Pointer atau JSON Patch untuk menunjuk dan mengedit lokasi yang tepat, dan Merge Patch untuk menggabungkan konfigurasi; patok Schema baru ke 2020-12 dan aktifkan allErrors agar semua kegagalan kembali dalam satu kali jalan.
Kueri adalah "menemukannya"; validasi adalah "memastikan itu benar". Masing-masing punya standar sendiri, jadi jangan mencampurnya.
1. Kueri path
Bagaimana sebaiknya memilih antara JSONPath dan JSON Pointer?
Tergantung apakah Anda "menemukan berdasarkan kondisi" atau "mengambil berdasarkan posisi".
| Dimensi | JSONPath | JSON Pointer |
|---|---|---|
| Standar | RFC 9535 (2024) | RFC 6901 (2013) |
| Ekspresi | $.users[?(@.age > 18)].id |
/users/0/id |
| Wildcard dan rekursi | Mendukung *, .. |
Tidak didukung |
| Pemfilteran bersyarat | Didukung | Tidak didukung |
| Hasil | Himpunan node (mungkin beberapa) | Satu lokasi |
| Daya ekspresi | Kuat, tetapi dengan warisan berat perbedaan implementasi | Lemah, tetapi sepenuhnya deterministik |
| Penggunaan tipikal | Ekstraksi log, debugging, eksplorasi data | JSON Patch, penunjukan konfigurasi, path kesalahan |
Aturan pemilihannya lugas:
- Perlu "mendapatkan semua node yang cocok dengan suatu kondisi" → JSONPath;
- Perlu "memastikan satu lokasi tepat lalu menambah, menghapus, atau mengubahnya" → JSON Pointer;
- Gunakan keduanya bersama: temukan node dengan JSONPath dan jelaskan lokasinya dengan Pointer.
Jangan gunakan JSONPath untuk penunjukan dalam kontrak API. JSONPath mengembalikan himpunan, dan tiga cabang untuk hasil kosong, satu elemen, serta banyak elemen membuat protokol menjadi rumit; Pointer berarti "entah menunjuk lokasi yang ada atau tidak ada", yang lebih cocok untuk kontrak.
Apa perbedaan $.. dan $.*?
$.. adalah penelusuran rekursif dan berjalan melalui setiap level; $.* hanya mengembalikan anak langsung dari level saat ini.
{
"a": { "id": 1, "child": { "id": 2 } },
"b": { "id": 3 }
}
$.*→ dua objek{id:1, child:{...}}dan{id:3};$..id→ tiga nilai:1,2,3;$.*.id→1dan3, tidak termasuk2yang bersarang.
Penelusuran rekursif praktis tetapi mahal: $.. berjalan melalui seluruh pohon, yang O(n) pada dokumen besar, dan mudah mengambil field dalam yang tidak Anda maksudkan.
Jika level konkret sudah cukup, hindari ...
Bisakah filter membandingkan string? Bisakah mencocokkan dengan regex?
Perbandingan string berfungsi; regex tergantung implementasi.
Sintaks standar (RFC 9535):
$.items[?(@.status == "active")]
$.items[?(@.price > 10 && @.stock > 0)]
$.items[?(@.name == $.target)] // bandingkan dengan node lain
Perbandingan string menggunakan tanda kutip ganda (kutip tunggal juga diperbolehkan dalam RFC 9535, tetapi sebagian implementasi lama hanya menerima kutip ganda).
Perbandingan numerik mengikuti semantik numerik; mencampur string dengan angka, seperti "10" > 9, adalah perilaku tak terdefinisi dalam standar, jadi jangan mengandalkannya.
Untuk regex, RFC 9535 mendefinisikan fungsi match() dan search(), tetapi dukungannya sangat bervariasi antar implementasi.
Pendekatan aman: biarkan JSONPath melakukan pemfilteran kasar, lalu lakukan pencocokan regex di kode setelah Anda memperoleh hasilnya.
Haruskah saya memakai JSON Patch atau Merge Patch?
Keduanya menggambarkan "bagaimana mengubah sesuatu", tetapi daya ekspresi dan semantiknya sangat berbeda.
| Dimensi | JSON Patch (RFC 6902) | Merge Patch (RFC 7386) |
|---|---|---|
| Bentuk | Array operasi [{"op":"add",...}] |
Objek parsial |
| Operasi yang didukung | add / remove / replace / move / copy / test | Hanya merge (penghapusan melalui null) |
| Dapat menunjuk elemen array | Ya, berdasarkan indeks | Tidak, seluruh array diganti |
| Atomisitas | test memungkinkan optimistic locking |
Tidak ada |
| Ukuran dokumen | Besar tetapi presisi | Kecil tetapi terbatas daya ekspresinya |
| Cocok untuk | Edit berbutir halus, pembaruan API parsial | Penggabungan konfigurasi, pengiriman formulir parsial |
Kriteria keputusan:
- Mengubah field elemen ketiga dalam array → JSON Patch;
- Menggabungkan beberapa entri konfigurasi ke konfigurasi yang ada → Merge Patch;
- Mendapatkan kontrol konkurensi "gagal jika nilai asli bukan X" →
testpada JSON Patch.
Perhatikan jebakan pada semantik null Merge Patch: null berarti "hapus key ini", bukan "tetapkan nilainya menjadi null".
Menetapkan nilai menjadi null tidak mungkin dilakukan dengan Merge Patch.
2. Validasi Schema
Masalah apa yang diselesaikan JSON Schema?
Ia mengubah "bagaimana data seharusnya terlihat" dari kesepakatan verbal menjadi kontrak yang dapat diperiksa mesin. Kesalahan yang benar-benar bisa dicegahnya:
- Field wajib yang hilang (
required); - Tipe salah, misalnya string padahal seharusnya angka (
type); - Nilai di luar rentang (
minimum/maximum/minLength); - Nilai di luar himpunan yang diizinkan (
enum); - Tipe elemen array tidak konsisten atau panjang array salah (
items/minItems); - Field hilang pada struktur bersarang (validasi rekursif melalui
properties); - Field tambahan (
additionalProperties: false).
Yang tidak bisa diselesaikannya: aturan bisnis ("waktu selesai harus lebih lambat dari waktu mulai"), kondisi lintas field (sebagian dapat diekspresikan dengan if/then,
tetapi keterbacaannya menurun), keunikan di basis data, dan pemeriksaan izin.
Jadi Schema adalah penjaga gerbang untuk "bentuk data", bukan pengganti logika bisnis.
Haruskah saya memakai draft-07 atau 2020-12?
Gunakan 2020-12 untuk proyek baru; untuk sistem yang sudah ada, periksa dukungan validator.
| Versi | Status | Perubahan penting |
|---|---|---|
| draft-04 | Usang | Banyak pustaka lama masih memakainya secara default |
| draft-07 | Standar de facto | Dukungan paling luas, ekosistem paling kaya |
| 2019-09 | Transisi | Memperkenalkan $defs, unevaluatedProperties |
| 2020-12 | Disarankan saat ini | items dipecah menjadi prefixItems + items, mendukung $dynamicRef |
Dua jebakan migrasi:
- Sintaks tuple berubah: di draft-07,
itemsbernilai array mengekspresikan tuple; 2020-12 memindahkannya keprefixItems; $refdan keyword saudara: di draft-07 keyword saudara diabaikan saat$refada; 2020-12 mengizinkan keduanya hidup bersama.
Jika validator hanya mendukung draft-07, menulis sintaks 2020-12 tetap akan diabaikan diam-diam — dan kasus paling berbahaya adalah "validasi lolos" padahal tidak ada yang divalidasi sama sekali.
Bagaimana cara menghasilkan tipe TypeScript dari Schema?
Dua rute: menurunkan tipe dari Schema, atau menghasilkan Schema dari tipe.
Dari Schema ke tipe (backend mendefinisikan kontrak, frontend memakainya):
import type { FromSchema } from 'json-schema-to-ts';
const userSchema = {
type: 'object',
properties: {
id: { type: 'string' },
age: { type: 'integer' },
},
required: ['id'],
additionalProperties: false,
} as const;
type User = FromSchema<typeof userSchema>;
// { id: string; age?: number }
Kuncinya adalah as const: tanpanya, type: 'string' disimpulkan sebagai string alih-alih literal 'string', dan inferensi menjadi rusak.
Dari tipe ke Schema (frontend sudah punya tipe dan membutuhkan validasi runtime):
import { z } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema';
const userSchema = z.object({ id: z.string(), age: z.number().int().optional() });
const jsonSchema = zodToJsonSchema(userSchema);
Keunggulan rute ini adalah "satu definisi menghasilkan tipe sekaligus validasi runtime"; biayanya adalah memperkenalkan sebuah DSL,
dan Schema yang dihasilkan mungkin membawa detail implementasi (misalnya struktur $ref yang tidak menarik).
Bagaimana cara menemukan field tertentu saat validasi gagal?
Lihat instancePath (2020-12) atau dataPath (draft-07) yang dikembalikan validator; itu adalah JSON Pointer ke lokasi kesalahan.
[
{
"instancePath": "/items/2/price",
"schemaPath": "#/properties/items/items/properties/price/type",
"keyword": "type",
"message": "must be number"
}
]
instancePath: "/items/2/price" memberi tahu Anda langsung: field price pada elemen ketiga array items memiliki tipe yang salah.
Dua saran praktis:
- Kembalikan semua kesalahan sekaligus alih-alih melempar pada kesalahan pertama — frontend yang memperbaiki semuanya dalam satu kali jalan lebih cepat daripada percobaan bolak-balik;
- Ubah Pointer menjadi bahasa awam sebelum menampilkannya kepada pengguna akhir;
/items/2/priceberguna bagi pengembang dan tidak berguna bagi pengguna.
allErrors: true adalah saklar di sebagian besar validator (Ajv mengharuskan mengaktifkannya secara eksplisit); defaultnya sering kali mode fail-fast.
Haruskah additionalProperties: false diaktifkan?
Aktifkan untuk kontrak internal; hati-hati dengan API publik.
Setelah diaktifkan, field apa pun yang tidak dideklarasikan di bawah properties akan gagal validasi. Manfaatnya adalah menangkap typo
(userNmae) dan pass-through yang tidak terduga; kerugiannya adalah penambahan field di hulu merusak konsumen hilir, menghancurkan kompatibilitas ke belakang.
Kompromi:
| Skenario | Rekomendasi |
|---|---|
| Antar layanan internal | Aktifkan. Perubahan field melewati manajemen versi |
| API eksternal publik | Nonaktifkan. Izinkan hulu menambah field |
| Mengonsumsi data pihak ketiga | Nonaktifkan. Anda tidak mengendalikan sisi lain |
| Validasi konfigurasi | Aktifkan. Typo dalam konfigurasi harus langsung gagal |
Bagaimana cara memvalidasi bahwa elemen array unik?
Dua semantik berbeda — jangan mencampurnya:
{ "uniqueItems": true }
Ini mendeduplikasi seluruh elemen, mensyaratkan bahwa array tidak memuat dua elemen yang setara secara mendalam.
{
"type": "array",
"items": { "type": "object", "properties": { "id": { "type": "string" } } },
"uniqueItems": true
}
Cuplikan di atas hanya menjamin bahwa objek secara keseluruhan tidak terduplikasi; {"id":"a","name":"x"} dan {"id":"a","name":"y"} tidak dianggap duplikat.
Untuk mendeduplikasi berdasarkan field tertentu, Schema tidak dapat mengekspresikannya; Anda harus memvalidasi di lapisan bisnis:
const ids = items.map((item) => item.id);
if (new Set(ids).size !== ids.length) {
throw new Error('id 重复');
}
Apakah format benar-benar memvalidasi email dan tanggal?
Belum tentu. Dalam spesifikasi, format secara default adalah "anotasi" bukan "asersi", sehingga validator boleh mengumpulkannya tanpa memvalidasinya.
- Ajv mengharuskan pemasangan dan konfigurasi paket
ajv-formatsyang terpisah, jika tidakformat: "email"praktis tidak berefek; - Sebagian validator memperlakukan
formatsebagai asersi yang aktif secara default, sehingga perilakunya tidak konsisten; - Bahkan ketika diaktifkan, regex
emailsangat longgar —a@blolos.
Kesimpulan: jangan memperlakukan format sebagai validasi yang kuat. Lakukan validasi ketat untuk email, URL, dan tanggal di kode bisnis Anda;
mempertahankan format di Schema sebagai dokumentasi tetap berharga, tetapi jangan berharap ia menghentikan data kotor.
3. Rujukan cepat
| Pertanyaan | Kesimpulan |
|---|---|
| Menemukan node berdasarkan kondisi | JSONPath |
| Penunjukan presisi, menerapkan patch | JSON Pointer |
| Mengubah elemen array | JSON Patch, Merge Patch tidak bisa melakukannya |
| Menggabungkan konfigurasi | Merge Patch |
| Versi Schema untuk proyek baru | 2020-12, perhatikan prefixItems |
| Menghasilkan tipe TS dari Schema | json-schema-to-ts + as const |
| Menemukan kesalahan validasi | Baca instancePath, aktifkan allErrors |
| Mendeduplikasi berdasarkan field | Schema tidak bisa; gunakan Set di lapisan bisnis |
format: email |
Sebagian besar validator tidak memeriksanya secara default, jadi jangan mengandalkannya |