Bandingkan JSON

Apa pun yang Anda masukkan di sini tidak disimpan, dicatat, atau dibagikan.

Tempel dua dokumen JSON untuk melihat setiap nilai yang ditambahkan, dihapus, dan diubah beserta jalurnya. Urutan kunci diabaikan, jadi hanya perbedaan nyata yang dilaporkan.

Cara memakai

  1. Tempel JSON asli di sebelah kiri.
  2. Tempel JSON pembanding di sebelah kanan.
  3. Perbedaannya muncul di bawah, lengkap dengan jalurnya.

Cara kerja perbandingan

Kedua dokumen diurai lalu ditelusuri secara paralel dari root, membandingkan apa pun yang ada di path yang sama di kedua sisi. Objek dicocokkan berdasarkan kunci, jadi mengubah urutan kunci tidak mengubah apa pun; array dicocokkan berdasarkan indeks. Setiap perbedaan dilaporkan sebagai salah satu dari tiga jenis: ditambahkan, artinya path hanya ada di sisi kanan; dihapus, artinya hanya ada di sisi kiri; dan diubah, artinya kedua sisi punya nilai di path itu tetapi nilainya berbeda.

Perubahan tipe dihitung sebagai perubahan, dan layak diperiksa dengan teliti saat muncul. Field yang tadinya angka 42 dan kini menjadi string "42" biasanya masih berjalan di klien yang longgar, lalu gagal di proses lanjutan yang melakukan perhitungan dengannya. Hal yang sama berlaku untuk field yang tadinya objek dan kini menjadi array berisi satu objek, kecelakaan yang umum pada kode yang menyusun respons secara kondisional.

Mengapa array dibandingkan berdasarkan indeks

Inilah keputusan desain yang paling memengaruhi hasil yang Anda lihat, jadi perlu dijelaskan terus terang. Sisipkan satu elemen di awal array berisi 200 elemen, dan perbandingan berbasis indeks akan melaporkan 200 perubahan, karena setiap elemen setelah titik penyisipan kini berada di indeks yang berbeda. Tidak ada yang salah; diff hanya menjawab pertanyaan yang berbeda dari yang Anda pikirkan.

Alternatifnya adalah mencari longest common subsequence dan melaporkan penyisipan, seperti yang dilakukan diff baris pada teks. Untuk JSON, cara ini belum tentu lebih baik. Cara itu mengharuskan penentuan kapan dua objek dalam daftar adalah objek yang sama, yang memerlukan kunci yang tidak Anda tentukan dan tidak bisa ditebak alat ini, dan saat tebakannya salah, hasilnya adalah jawaban yang meyakinkan tetapi menyesatkan. Perbandingan indeks memang kasar, tetapi tidak pernah mengarang kecocokan yang sebenarnya tidak ada.

Dalam praktiknya, jika daftar memang diurutkan ulang, urutkan kedua sisi berdasarkan identifier-nya sebelum membandingkan.

Null, kunci yang tidak ada, dan cara membaca path

Kunci yang ada dengan nilai null di satu sisi dan sama sekali tidak ada di sisi lain dilaporkan sebagai ditambahkan atau dihapus, bukan diubah, karena keduanya adalah keadaan yang berbeda dan kode memperlakukannya secara berbeda. Tidak ada biasanya berarti field itu tidak pernah diisi; null biasanya berarti sengaja diisi dengan kosong. API yang memakai yang satu untuk maksud yang lain menimbulkan bug, dan alat inilah yang menunjukkan mana yang sebenarnya Anda kirim.

Path dibaca sebagai rute dari root: user.roles[2] adalah elemen ketiga dari array roles di dalam objek user. Sintaks ini sengaja dibuat sama dengan yang Anda ketik di JavaScript untuk mengakses nilai tersebut, dan cukup mirip dengan ekspresi JSONPath sehingga bisa ditempel ke sebagian besar alat yang menerimanya.

Alur regresi yang menemukan perubahan sungguhan

Kegunaan paling umum dari diff JSON adalah memastikan sebuah deployment tidak mengubah apa pun yang seharusnya tidak berubah. Ambil respons dari versi yang sedang berjalan, ambil lagi dari versi kandidat, lalu bandingkan. Yang sulit tidak pernah perbandingannya sendiri, melainkan derau, karena sebuah respons biasanya memuat field yang memang wajar berbeda setiap kali.

Buang atau normalkan field seperti itu sebelum membandingkan: identifier yang dibangkitkan, timestamp, request id, URL bertanda tangan yang punya masa berlaku, dan apa pun yang melaporkan durasi. Skrip pendek yang menelusuri dokumen dan mengganti field semacam itu dengan nilai tetap akan mengubah diff berisi dua ratus perbedaan menjadi diff berisi satu perbedaan yang benar-benar penting.

Lakukan hal yang sama pada urutan di tempat yang tidak dijanjikan oleh API. Jika daftar tag kembali dengan urutan berbeda pada setiap panggilan, urutkan dulu kedua sisi memakai kunci yang stabil; kalau tidak, perbandingan berbasis indeks akan melaporkan setiap elemen sebagai berubah dan menyembunyikan satu-satunya perbedaan asli di antaranya.

Sama, identik, dan setara

Dua dokumen bisa disebut sama dalam beberapa pengertian yang berbeda, dan sebuah alat harus memilih salah satunya. Kesamaan byte adalah yang paling ketat sekaligus paling tidak berguna, sebab memformat ulang saja sudah merusaknya. Kesamaan struktural - kunci yang sama dengan nilai yang sama, terlepas dari spasi maupun urutan kunci - itulah yang dikerjakan perbandingan ini. Kesetaraan semantik adalah hal ketiga yang bergantung pada skema.

Angka adalah tempat perbandingan struktural menjadi canggung. JSON tidak membedakan bilangan bulat dari bilangan pecahan, sehingga bagi parser 1, 1.0, dan 1e0 adalah nilai yang sama, dan pilihan bentuk mana yang dikeluarkan penghasil data bisa berubah antarversi pustaka tanpa ada yang benar-benar berubah. Nol di akhir desimal hilang setelah satu putaran konversi karena alasan yang sama.

String bisa berbeda pada tingkat byte padahal terlihat identik, karena Unicode mengizinkan lebih dari satu cara menyandikan karakter yang sama: huruf beraksen dapat berupa satu code point atau huruf dasar ditambah tanda gabungan. Menormalkan ke NFC sebelum membandingkan menghapus jenis perbedaan palsu ini, dan layak dilakukan setiap kali teks datang dari lebih dari satu sumber.

Saat diff bukan alat yang tepat

Membandingkan dua contoh hanya memberi tahu Anda tentang dua contoh itu. Memeriksa apakah respons mematuhi sebuah aturan adalah pekerjaan lain, dan alatnya adalah skema: JSON Schema untuk dokumen, OpenAPI untuk endpoint. Skema menyatakan bahwa id bertipe bilangan bulat dan email cocok dengan suatu pola, dan itu berlaku untuk setiap respons, bukan hanya untuk sepasang data yang kebetulan Anda tangkap.

Dalam praktik keduanya saling melengkapi. Validasi skema di continuous integration menangkap pergeseran struktur - field yang berganti tipe, properti wajib yang lenyap - sementara diff menjawab pertanyaan yang lebih sempit, yaitu apa yang berubah di antara dua payload tertentu saat Anda sedang menyelidiki sesuatu.

Contract testing melangkah lebih jauh dengan meminta pihak konsumen mempublikasikan apa saja yang ia andalkan, sehingga pipeline pihak penghasil gagal ketika sebuah perubahan akan merusak klien nyata, bukan sekadar ketika hasilnya berbeda dari contoh lama.

Membaca sebuah path dan langsung menujunya

Path yang dilaporkan di sini terbaca seperti rute dari akar: user.roles[2] adalah elemen ketiga dari array roles di dalam objek user. Bentuk itu sengaja dibuat sama dengan ekspresi yang akan Anda ketik di JavaScript untuk mencapai nilainya, sehingga sebuah path bisa langsung ditempel ke konsol selama dokumennya tersimpan dalam sebuah variabel.

Bentuk yang sama juga dekat dengan JSONPath, bahasa kueri yang diterima sebagian besar alat log dan API, yang ekspresinya diawali tanda dolar: $.user.roles[2]. jq, pemroses baris perintah itu, memakai notasi serupa tanpa tanda dolar, jadi .user.roles[2] mengambil nilai yang sama dari sebuah berkas atau aliran data.

Untuk dokumen yang sangat besar, path itulah keluaran yang berguna, bukan diff-nya. Begitu Anda tahu di mana letak perbedaannya, jq atau filter di klien HTTP Anda bisa menarik hanya subpohon itu dari kedua sisi, dan itu jauh lebih cepat dibaca daripada tampilan dokumen utuh mana pun.

Membandingkan dokumen yang terlalu besar untuk dibaca

Respons berukuran beberapa megabyte lazim ditemui pada ekspor analitik dan endpoint massal, dan membandingkannya secara utuh menghasilkan keluaran yang tak bisa dipakai siapa pun. Pendekatan yang produktif adalah menyusutkan sebelum membandingkan: pilih subpohon yang Anda pedulikan, proyeksikan hanya field yang sedang dibahas, dan urutkan koleksi berdasarkan identifier-nya.

Menghitung jumlah adalah langkah awal yang berguna. Jika satu sisi punya 4.812 record dan sisi lain 4.811, pertanyaan yang menarik adalah record mana yang hilang, dan itu adalah perbandingan himpunan identifier, bukan diff dokumen - dua daftar id terurut ditambah diff berbasis baris menjawabnya dalam hitungan detik.

Ketika payload memang besar dan perbandingan harus dijalankan berulang kali, pipeline baris perintah mengalahkan peramban: pakai jq untuk menormalkan kedua berkas menjadi baris ringkas yang terurut, lalu jalankan diff. Keduanya sama-sama berjalan secara lokal, tetapi baris perintah tidak punya halaman yang harus tetap responsif.

Pertanyaan umum

Apakah urutan kunci memengaruhi perbandingan?
Tidak. Objek dibandingkan berdasarkan kunci, bukan posisi, jadi menukar urutan kunci tidak menghasilkan perbedaan. Elemen array dibandingkan berdasarkan indeks, karena pada array urutan memang bermakna.
Apakah data saya diunggah ke server?
Tidak. Perbandingan berjalan sepenuhnya di peramban Anda memakai JavaScript. Kedua dokumen tidak keluar dari perangkat, sehingga aman untuk respons API produksi.
Apa arti jalur seperti user.roles[2]?
Jalur itu menunjukkan letak perbedaannya di dalam dokumen: elemen ketiga dari array roles, di dalam objek user. Titik memisahkan kunci objek dan kurung siku menunjuk indeks array.
Apa bedanya nilai null dengan kunci yang hilang?
Keduanya dilaporkan berbeda. Kunci yang ada dengan nilai null di satu sisi dan tidak ada di sisi lain ditampilkan sebagai ditambahkan atau dihapus, bukan sebagai perubahan.

Terakhir diperbarui:

Laporkan masalah

Sertakan langkah, hasil yang diharapkan, dan hasil sebenarnya. Hapus rahasia dan data pribadi.

Alat terkait