JSON Toolkit

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.

Menengah±12 menit bacaDiterbitkan 2026-10-30Diperbarui 2026-10-30
Sampul untuk FAQ kueri dan validasi

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 → 1 dan 3, tidak termasuk 2 yang 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" → test pada 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:

  1. Sintaks tuple berubah: di draft-07, items bernilai array mengekspresikan tuple; 2020-12 memindahkannya ke prefixItems;
  2. $ref dan keyword saudara: di draft-07 keyword saudara diabaikan saat $ref ada; 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:

  1. Kembalikan semua kesalahan sekaligus alih-alih melempar pada kesalahan pertama — frontend yang memperbaiki semuanya dalam satu kali jalan lebih cepat daripada percobaan bolak-balik;
  2. Ubah Pointer menjadi bahasa awam sebelum menampilkannya kepada pengguna akhir; /items/2/price berguna 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-formats yang terpisah, jika tidak format: "email" praktis tidak berefek;
  • Sebagian validator memperlakukan format sebagai asersi yang aktif secara default, sehingga perilakunya tidak konsisten;
  • Bahkan ketika diaktifkan, regex email sangat longgar — a@b lolos.

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