GoSuda

Perbandingan Go 1.25 encoding/json v1 vs v2

By lemonmint
views ...

encoding/json package v2 dari Go merupakan implementasi baru yang bertujuan untuk memperbaiki beberapa kekurangan dari v1 yang ada (kurangnya konsistensi, perilaku mengejutkan, masalah kinerja). Ini adalah fitur eksperimental yang diaktifkan melalui tag build goexperiment.jsonv2.

Poin terpenting adalah ketika v2 diaktifkan, v1 akan beroperasi sebagai lapisan kompatibilitas yang mengemulasi perilaku v1 di atas implementasi v2. Hal ini dicapai melalui fungsi DefaultOptionsV1() di berkas v2_v1.go. Artinya, v2 menyediakan opsi yang dapat mereplikasi perilaku v1 secara sempurna, sekaligus memperkenalkan perilaku default baru yang lebih ketat dan dapat diprediksi.

Tujuan utama v2 adalah sebagai berikut:

  1. Peningkatan Akurasi dan Prediktabilitas: Secara default menerapkan aturan yang lebih ketat (misalnya, peka huruf besar/kecil, melarang kunci duplikat) untuk mengurangi perilaku yang tidak terduga.
  2. Peningkatan Kinerja: Merancang ulang mesin parsing dan encoding untuk meningkatkan efisiensi.
  3. Peningkatan Fleksibilitas dan Kontrol: Memperkenalkan sistem opsi yang terperinci untuk memungkinkan pengembang mengontrol pemrosesan JSON secara tepat.

Perbedaan Utama dalam Makna/Perilaku

Perbedaan perilaku antara v1 dan v2 telah dirangkum berdasarkan item, dengan fokus pada berkas v2_test.go.

1. Pencocokan Nama Bidang (Peka Huruf Besar/Kecil)

  • Perilaku v1: Ketika unmarshaling anggota objek JSON ke bidang struktur Go, pencocokan dilakukan tidak peka huruf besar/kecil (case-insensitive). Baik "FirstName" maupun "firstname" akan dipetakan ke bidang FirstName.
  • Perilaku v2: Secara default, pencocokan dilakukan peka huruf besar/kecil (case-sensitive), hanya memetakan bidang yang cocok persis.
  • Alasan Perubahan: Pencocokan yang tidak peka huruf besar/kecil dapat menjadi sumber perilaku yang tidak terduga dan menyebabkan penurunan kinerja saat memproses bidang yang tidak cocok. v2 mengadopsi perilaku yang lebih jelas dan dapat diprediksi sebagai standar.
  • Opsi Terkait: Dalam v2, Anda dapat secara eksplisit mengaktifkan pencocokan tidak peka huruf besar/kecil per bidang menggunakan opsi tag json:"...,case:ignore", atau menerapkan opsi json.MatchCaseInsensitiveNames(true) secara global.

2. Perubahan Makna Opsi Tag omitempty

  • Perilaku v1: Menghilangkan bidang berdasarkan "keadaan kosong" dari nilai Go. "Keadaan kosong" di sini berarti false, 0, pointer/antarmuka nil, array/slice/map/string dengan panjang 0.
  • Perilaku v2: Menghilangkan bidang berdasarkan "keadaan kosong" dari nilai JSON yang di-encode. Artinya, akan dihilangkan jika di-encode sebagai null, "", {}, [].
  • Alasan Perubahan: Definisi v1 tergantung pada sistem tipe Go. v2 menyediakan perilaku yang lebih konsisten berdasarkan sistem tipe JSON. Misalnya, dalam v1, nilai false dari tipe bool dihilangkan, tetapi dalam v2, false tidak dihilangkan karena bukan nilai JSON yang kosong. Dalam v2, opsi omitzero ditambahkan untuk menggantikan perilaku omitempty v1 yang diterapkan pada 0 atau false.
  • Opsi Terkait: Jika Anda menginginkan perilaku yang sama dengan v1 dalam v2, gunakan opsi json.OmitEmptyWithLegacyDefinition(true).

3. Perubahan Perilaku Opsi Tag string

  • Perilaku v1: Diterapkan pada bidang tipe angka, boolean, dan string. Nilai tersebut di-encode ulang di dalam string JSON (misalnya: int(42) -> "42"). Tidak diterapkan secara rekursif pada nilai di dalam tipe komposit (slice, map, dll.).
  • Perilaku v2: Hanya diterapkan pada tipe angka dan diterapkan secara rekursif. Artinya, angka di dalam slice seperti []int juga akan di-encode sebagai string JSON.
  • Alasan Perubahan: Tujuan utama opsi string adalah untuk merepresentasikan angka sebagai string untuk mencegah hilangnya presisi pada bilangan bulat 64-bit. Perilaku v1 terbatas dan kurang konsisten. v2 berfokus pada tujuan inti ini dan memperluas perilakunya secara rekursif untuk membuatnya lebih berguna.
  • Opsi Terkait: Anda dapat meniru perilaku v1 dengan opsi json.StringifyWithLegacySemantics(true).

4. Marshaling Slice dan Map nil

  • Perilaku v1: Slice nil dan map nil di-marshal sebagai null.
  • Perilaku v2: Secara default, slice nil di-marshal sebagai [] (array kosong), dan map nil di-marshal sebagai {} (objek kosong).
  • Alasan Perubahan: nil adalah detail implementasi bahasa Go, dan mengeksposnya ke format JSON yang tidak tergantung bahasa tidaklah diinginkan. [] atau {} yang mewakili koleksi kosong adalah ekspresi yang lebih umum.
  • Opsi Terkait: Dalam v2, Anda dapat me-marshal sebagai null seperti v1 melalui opsi json.FormatNilSliceAsNull(true) atau json.FormatNilMapAsNull(true).

5. Unmarshaling Array

  • Perilaku v1: Ketika unmarshaling ke array Go ([N]T), tidak akan menghasilkan kesalahan meskipun panjang array JSON berbeda dengan panjang array Go. Jika panjangnya lebih pendek, sisa ruang akan diisi dengan nilai nol, dan jika lebih panjang, kelebihannya akan dibuang.
  • Perilaku v2: Panjang array JSON harus sama persis dengan panjang array Go. Jika tidak, akan terjadi kesalahan.
  • Alasan Perubahan: Dalam Go, array berukuran tetap seringkali memiliki arti penting. Perilaku v1 dapat menyebabkan hilangnya data secara diam-diam. v2 meningkatkan akurasi dengan aturan yang lebih ketat.
  • Opsi Terkait: Anda dapat meniru perilaku v1 dengan opsi json.UnmarshalArrayFromAnyLength(true).

6. Pemrosesan time.Duration

  • Perilaku v1: time.Duration diperlakukan secara internal sebagai int64 dan di-encode sebagai angka JSON dalam unit nanodetik.
  • Perilaku v2: Menggunakan metode time.Duration.String() untuk di-encode sebagai string JSON dalam format seperti "1h2m3s".
  • Alasan Perubahan: Nanodetik angka kurang terbaca, dan representasi string standar dari time.Duration lebih berguna.
  • Opsi Terkait: Anda dapat menggunakan perilaku v1 melalui opsi tag json:",format:nano" atau opsi json.FormatTimeWithLegacySemantics(true).

7. Pemrosesan UTF-8 yang Tidak Valid

  • Perilaku v1: Saat marshaling/unmarshaling, jika ada byte UTF-8 yang tidak valid dalam string, secara diam-diam diganti dengan karakter pengganti Unicode (\uFFFD).
  • Perilaku v2: Secara default, jika menemukan UTF-8 yang tidak valid, mengembalikan kesalahan.
  • Alasan Perubahan: Untuk mencegah kerusakan data secara diam-diam dan untuk mematuhi standar JSON yang lebih ketat (RFC 7493).
  • Opsi Terkait: Anda dapat meniru perilaku v1 dengan opsi jsontext.AllowInvalidUTF8(true).

8. Pemrosesan Nama Anggota Objek Duplikat

  • Perilaku v1: Mengizinkan nama anggota yang sama muncul berulang kali di dalam objek JSON. Nilai yang muncul terakhir akan menimpa nilai sebelumnya.
  • Perilaku v2: Secara default, jika ada nama anggota duplikat, mengembalikan kesalahan.
  • Alasan Perubahan: Standar RFC 8259 tidak mendefinisikan perilaku nama duplikat, sehingga dapat berperilaku berbeda di setiap implementasi. Ini dapat menjadi sumber kerentanan keamanan. v2 secara eksplisit menolak ini untuk meningkatkan akurasi dan keamanan.
  • Opsi Terkait: Anda dapat meniru perilaku v1 dengan opsi jsontext.AllowDuplicateNames(true).

Perbedaan Implementasi dan Arsitektur

  • v1: Sangat bergantung pada decodeState di decode.go dan state machine yang ditulis secara manual di scanner.go. Ini adalah struktur monolitik di mana logika parsing dan analisis semantik sangat erat terikat.
  • v2: Arsitekturnya lebih modular.
    • encoding/json/jsontext: Menyediakan tokenizer JSON (Decoder) dan encoder (Encoder) berkinerja tinggi tingkat rendah. Paket ini hanya berfokus pada aspek sintaksis JSON.
    • encoding/json/v2: Berdasarkan jsontext, menangani konversi semantik antara tipe Go dan nilai JSON.
    • Pemecahan ini memisahkan analisis sintaksis dan semantik, meningkatkan kejelasan kode dan kinerja.

API dan Fitur Baru v2

v2 menyediakan kemampuan kontrol yang sangat fleksibel melalui sistem json.Options.

  • json.Options: Sekumpulan opsi yang mengubah perilaku marshaling/unmarshaling.
  • json.JoinOptions(...): Menggabungkan beberapa opsi menjadi satu.
  • WithMarshalers / WithUnmarshalers: Fitur canggih yang memungkinkan injeksi logika serialisasi/deserialisasi untuk tipe tertentu tanpa harus mengimplementasikan antarmuka Marshaler/Unmarshaler. Ini sangat berguna saat memproses tipe dari paket eksternal.
  • Opsi Baru: Berbagai kontrol perilaku yang tidak mungkin dilakukan di v1, seperti RejectUnknownMembers, Deterministic(false), FormatNilSliceAsNull, kini tersedia.

Kesimpulan

encoding/json v2 adalah implementasi modern yang sangat meningkatkan akurasi, kinerja, dan fleksibilitas berdasarkan pengalaman v1. Meskipun perilaku defaultnya lebih ketat, sistem Options yang canggih mendukung semua perilaku v1 secara sempurna, memungkinkan pengenalan keuntungan v2 secara bertahap sambil mempertahankan kompatibilitas dengan kode yang ada.

  • Untuk proyek baru, disarankan untuk menggunakan v2 sebagai standar.
  • Proyek yang sudah ada dapat terus menggunakan jsonv1, atau mengadopsi strategi migrasi ke jsonv2 dengan memperkenalkan perilaku v2 yang lebih ketat secara bertahap melalui DefaultOptionsV1().