Membandingkan data berformat JSON seperti respons API, file konfigurasi, dan file terjemahan cukup sering dilakukan. Namun, jika dua JSON dibandingkan apa adanya sebagai teks, sering kali muncul banyak perbedaan padahal datanya sama. Artikel ini menjelaskan penyebab dan cara mengatasinya.
Mengapa muncul perbedaan semu
Standar JSON (RFC 8259) mendefinisikan objek sebagai "kumpulan pasangan nama/nilai yang tidak berurutan". Artinya {"a":1,"b":2} dan {"b":2,"a":1} menyatakan data yang sama. Namun, sebagai teks urutan barisnya berbeda, sehingga diff menganggapnya berbeda. Selain itu, perbedaan berikut juga muncul tanpa ada hubungannya dengan data.
- Lebar indentasi (2 spasi, 4 spasi, tab) dan JSON yang dipadatkan menjadi satu baris
- Ada tidaknya spasi setelah titik dua (
"a":1dan"a": 1) - Penulisan angka (
1.0dan1,1e2dan100) - Escape pada string (
"\u00e9"dan"é")
Solusi: parse lalu tulis ulang dalam bentuk yang sama
Cara yang paling pasti adalah mem-parse kedua JSON menjadi data, lalu menserialisasinya kembali dengan aturan yang sama.
- Parse kedua teks sebagai JSON. Jika ada galat sintaks, perbaiki dulu.
- Urutkan kunci semua objek secara rekursif.
- Tulis ulang dengan indentasi yang sama (misalnya 2 spasi).
- Bandingkan hasilnya per baris.
Misalnya, dua JSON berikut hanya berbeda urutan kunci dan urutan array.
{"name":"kim","age":30,"tags":["a","b"]}
{"age":30,"name":"kim","tags":["b","a"]}
Setelah diurutkan dan ditulis ulang, perbedaan urutan kunci hilang dan yang tersisa hanyalah perbedaan sebenarnya, yaitu urutan array.
@@ -2,7 +2,7 @@ "age": 30, "name": "kim", "tags": [- "a",- "b"+ "b",+ "a" ] }
Array tidak diurutkan
Berbeda dengan objek, urutan dalam array memiliki makna. ["a","b"] dan ["b","a"] adalah data yang berbeda. Karena itu, prinsipnya array tidak diurutkan saat normalisasi. Namun, untuk array yang urutannya tidak penting secara bisnis seperti daftar tag, mengurutkannya sendiri sebelum membandingkan membuat hasilnya lebih mudah dibaca. Mana yang benar tergantung pada makna datanya.
Hal yang diubah oleh proses parse
Cara parse lalu tulis ulang punya efek samping yang perlu diketahui.
- Penulisan angka diseragamkan.
1.0dan1sama-sama menjadi1sehingga terlihat sama. Umumnya ini diinginkan, tetapi jika penulisannya sendiri penting, bandingkan juga teks aslinya. - Presisi bilangan bulat besar. JavaScript menangani angka sebagai floating point 64 bit, sehingga bilangan bulat yang lebih besar dari
Number.MAX_SAFE_INTEGER(2^53 − 1 = 9007199254740991) tidak bisa dinyatakan dengan tepat. Misalnya,9007199254740993menjadi9007199254740992setelah di-parse. Waspadai hal ini pada JSON yang menyimpan ID panjang sebagai angka. - Kunci ganda. RFC 8259 hanya menganjurkan (SHOULD) agar nama dalam objek unik, dan penanganan kunci ganda berbeda-beda tiap implementasi.
JSON.parsedi JavaScript menyimpan nilai terakhir. - Komentar dan koma di akhir.
// commentdan koma di akhir seperti[1, 2,]bukan JSON standar (diizinkan dalam format perluasan seperti JSON5 dan JSONC). Parser standar akan menghasilkan galat.
Cara di baris perintah
Jika jq terpasang, opsi -S (--sort-keys) dapat mengeluarkan JSON dengan kunci yang diurutkan.
jq -S . before.json > a.json
jq -S . after.json > b.json
diff -u a.json b.json
Perbedaan dengan JSON Patch
Hasil diff adalah daftar perubahan per baris yang ditujukan untuk dibaca manusia. Sementara itu, JSON Patch (RFC 6902) adalah standar yang menyatakan perubahan dengan path di dalam struktur JSON seperti {"op":"replace","path":"/age","value":31}, dan JSON Merge Patch (RFC 7396) adalah cara menimpakan JSON yang hanya berisi bagian yang berubah ke data asli. Jika perubahan dipertukarkan antarprogram, standar-standar ini yang cocok; jika manusia yang meninjau, diff teks setelah pengurutan lebih cocok.
Coba di alat ini
Jika opsi Urutkan kunci JSON di Pembanding Teks diaktifkan, kedua sisi di-parse, kuncinya diurutkan, lalu ditulis ulang dengan indentasi 2 spasi sebelum dibandingkan. Urutan array tidak diubah, dan jika parse gagal, alat ini memberi tahu di sisi mana dan pada karakter ke berapa galat terjadi. Anda juga bisa menyeret dua file .json sekaligus. Untuk membaca hasil sebagai patch, lihat Cara membaca unified diff.