---
id: 61f2009278a5f7366f79395da59447cf
author: lemonmint
title: Analisis perubahan perilaku encoding/json/v2 pada Go 1.25 dan lapisan kompatibilitas v1
description: Penelitian ini membandingkan pencocokan yang peka terhadap huruf besar/kecil, perubahan makna omitempty/omitzero, serta serialisasi dan opsi untuk slice/map nil pada encoding/json/v2, yang diperkenalkan sebagai fitur eksperimental Go 1.25 (goexperiment.jsonv2).
language: id
date: 2025-06-20T05:38:20.100520237Z
path: /blog/posts/go-1-25-encoding-json-v1-vs-v2-z74a36b08
category: release
---

Paket `encoding/json` v2 di Go merupakan implementasi baru yang dirancang untuk memperbaiki sejumlah kelemahan v1, yaitu kurangnya konsistensi, perilaku yang mengejutkan, dan masalah performa. Fitur ini bersifat eksperimental dan diaktifkan melalui build tag `goexperiment.jsonv2`.

Hal yang paling penting adalah **ketika v2 diaktifkan, v1 beroperasi sebagai lapisan kompatibilitas yang meniru perilaku v1 di atas implementasi v2**. Hal ini dilakukan melalui fungsi `DefaultOptionsV1()` dalam file `v2_v1.go`. Dengan demikian, v2 menyediakan opsi yang memungkinkan perilaku v1 direproduksi secara lengkap, sekaligus menawarkan perilaku default baru yang lebih ketat dan dapat diprediksi.

Tujuan utama v2 adalah sebagai berikut.

1. **Peningkatan ketepatan dan prediktabilitas**: Dengan menerapkan aturan default yang lebih ketat (misalnya, sensitivitas huruf besar/kecil dan larangan kunci duplikat), v2 mengurangi perilaku yang tidak diharapkan.
2. **Peningkatan performa**: Mesin parsing dan encoding dirancang ulang untuk meningkatkan efisiensi.
3. **Perluasan fleksibilitas dan kendali**: Sistem opsi yang terperinci diperkenalkan agar pengembang dapat mengontrol cara pemrosesan JSON secara rinci.

---

## Perbedaan Perilaku dan Makna Utama

Perbedaan perilaku antara v1 dan v2 disusun per item dengan berfokus pada file `v2_test.go`.

### 1. Pencocokan Nama Field (Sensitivitas Huruf Besar/Kecil)

- **Perilaku v1**: Saat meng-unmarshal anggota objek JSON ke field struct Go, pencocokan dilakukan **tanpa memperhatikan huruf besar/kecil (case-insensitive)**. Baik `"FirstName"` maupun `"firstname"` akan dipetakan ke field `FirstName`.
- **Perilaku v2**: Secara default, pencocokan **peka terhadap huruf besar/kecil (case-sensitive)**, sehingga hanya field yang namanya cocok persis yang dipetakan.
- **Alasan perubahan**: Pencocokan yang mengabaikan huruf besar/kecil dapat menjadi penyebab perilaku yang tidak diharapkan, serta menimbulkan penurunan performa saat memproses field yang tidak cocok. v2 mengadopsi perilaku yang lebih jelas dan dapat diprediksi sebagai default.
- **Opsi terkait**: Di v2, pengabaian huruf besar/kecil dapat diaktifkan secara eksplisit per field menggunakan opsi tag `json:"...,case:ignore"`, atau diterapkan secara global dengan `json.MatchCaseInsensitiveNames(true)`.

### 2. Perubahan Makna Opsi Tag `omitempty`

- **Perilaku v1**: Field dihilangkan berdasarkan "keadaan kosong (empty)" dari nilai Go. "Keadaan kosong" di sini berarti `false`, `0`, pointer/interface `nil`, serta array/slice/map/string dengan panjang nol.
- **Perilaku v2**: Field dihilangkan berdasarkan "keadaan kosong" dari nilai JSON yang dihasilkan encoding. Artinya, field akan dihilangkan jika di-encode menjadi `null`, `""`, `{}`, atau `[]`.
- **Alasan perubahan**: Definisi v1 bergantung pada sistem tipe Go. v2 menggunakan sistem tipe JSON sebagai acuan sehingga memberikan perilaku yang lebih konsisten. Sebagai contoh, pada v1 nilai `false` pada tipe `bool` dihilangkan, sedangkan pada v2 `false` merupakan nilai JSON yang tidak kosong sehingga tidak dihilangkan. Di v2, opsi `omitzero` ditambahkan untuk menggantikan perilaku `omitempty` v1 yang diterapkan pada `0` atau `false`.
- **Opsi terkait**: Untuk memperoleh perilaku yang sama dengan v1 di v2, gunakan opsi `json.OmitEmptyWithLegacyDefinition(true)`.

### 3. Perubahan Perilaku Opsi Tag `string`

- **Perilaku v1**: Diterapkan pada field bertipe angka, boolean, dan string. Nilai tersebut di-encode ulang di dalam string JSON (contoh: `int(42)` menjadi `"42"`). Penerapan tidak bersifat rekursif pada nilai di dalam tipe komposit (slice, map, dan sebagainya).
- **Perilaku v2**: **Hanya diterapkan pada tipe angka**, dan bersifat rekursif. Artinya, angka di dalam slice seperti `[]int` juga akan di-encode sebagai string JSON.
- **Alasan perubahan**: Kegunaan utama opsi `string` adalah merepresentasikan angka sebagai string untuk mencegah hilangnya presisi pada integer 64-bit. Perilaku v1 terbatas dan kurang konsisten. v2 berfokus pada kegunaan inti ini dan memperluas perilakunya secara rekursif agar lebih bermanfaat.
- **Opsi terkait**: Perilaku v1 dapat ditiru dengan opsi `json.StringifyWithLegacySemantics(true)`.

### 4. Marshal Slice dan Map `nil`

- **Perilaku v1**: Slice `nil` dan map `nil` di-marshal menjadi `null`.
- **Perilaku v2**: Secara default, slice `nil` di-marshal menjadi `[]` (array kosong), dan map `nil` menjadi `{}` (objek kosong).
- **Alasan perubahan**: `nil` adalah detail implementasi bahasa Go, dan memperlihatkannya dalam format JSON yang tidak bergantung pada bahasa tidak diinginkan. `[]` dan `{}` merupakan representasi koleksi kosong yang lebih umum.
- **Opsi terkait**: Di v2, marshal menjadi `null` seperti v1 dapat dilakukan dengan opsi `json.FormatNilSliceAsNull(true)` atau `json.FormatNilMapAsNull(true)`.

### 5. Unmarshal Array

- **Perilaku v1**: Saat melakukan unmarshal ke array Go (`[N]T`), tidak terjadi error meskipun panjang array JSON berbeda dengan panjang array Go. Jika array JSON lebih pendek, sisa ruang diisi dengan nilai nol, dan jika lebih panjang, kelebihannya dibuang.
- **Perilaku v2**: Panjang array JSON harus **tepat sama** dengan panjang array Go. Jika tidak, akan terjadi error.
- **Alasan perubahan**: Di Go, panjang array berukuran tetap sering kali memiliki makna yang penting. Perilaku v1 dapat menyebabkan kehilangan data secara diam-diam. v2 menerapkan aturan yang lebih ketat untuk meningkatkan ketepatan.
- **Opsi terkait**: Perilaku v1 dapat ditiru dengan opsi `json.UnmarshalArrayFromAnyLength(true)`.

### 6. Penanganan `time.Duration`

- **Perilaku v1**: `time.Duration` secara internal diperlakukan sebagai `int64` dan di-encode sebagai angka JSON dalam satuan nanodetik.
- **Perilaku v2**: Di-encode sebagai string JSON dalam format seperti `"1h2m3s"` dengan menggunakan metode `time.Duration.String()`.
- **Alasan perubahan**: Angka nanodetik kurang mudah dibaca, sedangkan representasi string standar dari `time.Duration` lebih berguna.
- **Opsi terkait**: Perilaku v1 dapat digunakan melalui opsi tag `json:",format:nano"` atau opsi `json.FormatTimeWithLegacySemantics(true)`.

### 7. Penanganan UTF-8 Tidak Valid

- **Perilaku v1**: Saat marshal/unmarshal, byte UTF-8 yang tidak valid di dalam string **diganti secara diam-diam** dengan karakter pengganti Unicode (`\uFFFD`).
- **Perilaku v2**: Secara default, ketika menemukan UTF-8 yang tidak valid, akan **dikembalikan error**.
- **Alasan perubahan**: Untuk mencegah kerusakan data yang tidak disadari serta mengikuti standar JSON yang lebih ketat (RFC 7493).
- **Opsi terkait**: Perilaku v1 dapat ditiru dengan opsi `jsontext.AllowInvalidUTF8(true)`.

### 8. Penanganan Nama Anggota Objek yang Duplikat

- **Perilaku v1**: Membolehkan anggota dengan nama yang sama muncul lebih dari sekali dalam sebuah objek JSON. Nilai yang muncul terakhir akan menimpa nilai sebelumnya.
- **Perilaku v2**: Secara default, jika terdapat nama anggota yang duplikat, akan **dikembalikan error**.
- **Alasan perubahan**: Standar RFC 8259 tidak mendefinisikan perilaku untuk nama duplikat sehingga implementasi yang berbeda dapat berperilaku berbeda. Hal ini dapat menjadi penyebab kerentanan keamanan. v2 secara eksplisit menolaknya untuk meningkatkan ketepatan dan keamanan.
- **Opsi terkait**: Perilaku v1 dapat ditiru dengan opsi `jsontext.AllowDuplicateNames(true)`.

---

## Perbedaan Implementasi dan Arsitektur

- **v1**: Sangat bergantung pada `decodeState` di `decode.go` dan mesin state (state machine) yang ditulis secara manual di `scanner.go`. Ini adalah struktur monolitik dengan logika parsing dan analisis semantik yang saling terikat erat.
- **v2**: Arsitekturnya lebih modular.
  - **`encoding/json/jsontext`**: Menyediakan tokenizer JSON tingkat rendah dan berperforma tinggi, yaitu `Decoder`, serta encoder `Encoder`. Paket ini hanya berfokus pada aspek sintaksis JSON.
  - **`encoding/json/v2`**: Berdasarkan `jsontext`, paket ini menangani konversi semantik antara tipe Go dan nilai JSON.
  - Melalui pemisahan ini, analisis sintaksis dan semantik dipisahkan sehingga kejelasan kode dan performa meningkat.

---

## API dan Fitur Baru v2

v2 menyediakan kendali yang sangat fleksibel melalui sistem `json.Options`.

- **`json.Options`**: Kumpulan opsi yang mengubah perilaku marshal/unmarshal.
- **`json.JoinOptions(...)`**: Menggabungkan beberapa opsi menjadi satu.
- **`WithMarshalers` / `WithUnmarshalers`**: Fitur yang kuat untuk menyisipkan logika serialisasi/deserialisasi pada tipe tertentu tanpa perlu mengimplementasikan interface `Marshaler`/`Unmarshaler`. Fitur ini sangat berguna saat menangani tipe dari paket eksternal.
- **Opsi baru**: `RejectUnknownMembers`, `Deterministic(false)`, `FormatNilSliceAsNull`, dan lainnya memungkinkan kendali perilaku yang tidak mungkin dilakukan di v1.

---

## Kesimpulan

`encoding/json` v2 merupakan implementasi modern yang secara signifikan meningkatkan **ketepatan, performa, dan fleksibilitas** berdasarkan pengalaman dari v1. Meskipun perilaku default menjadi lebih ketat, sistem `Options` yang canggih mendukung seluruh perilaku v1 secara lengkap sehingga kompatibilitas dengan kode yang ada tetap terjaga dan keunggulan v2 dapat diadopsi secara bertahap.

- Untuk **proyek baru**, disarankan menggunakan v2 sebagai default.
- Untuk **proyek yang sudah ada**, dapat digunakan `jsonv1` apa adanya, atau bermigrasi ke `jsonv2` sambil secara bertahap memperkenalkan perilaku v2 yang lebih ketat melalui `DefaultOptionsV1()`.
