Data dinamis seperti hasil parsing JSON sering kali tidak memiliki skema tetap pada saat kompilasi. Bila kita tetap ingin mengakses field JSON melalui property bertipe, pola klasik adalah menyimpan Map<String, Any?> lalu membungkusnya di class dengan property yang melakukan map["nama"] di getter. Kotlin menyingkat pola ini lewat delegation langsung ke Map atau MutableMap. Pada chapter ini kita akan membahas bagaimana val x: T by map menerjemahkan akses property menjadi map["x"], kapan memakai versi mutable, serta batasannya bila tipe penting dan risiko ClassCastException muncul.
Kontrak Map Delegation #
Standard library menyediakan extension function getValue pada Map<String, Any?> dan setValue pada MutableMap<String, Any?>. Kompilator menerjemahkan ekspresi val x: T by map menjadi map.getValue(property.name) — key yang dipakai adalah nama property, bukan tipe atau posisi.
class Pengguna(val data: Map<String, Any?>) {
val nama: String by data // membaca data["nama"] sebagai String
val umur: Int by data // membaca data["umur"] sebagai Int
}Bila key tidak ada di map, eksekusi melemparkan NoSuchElementException (karena getValue pada Map berperilaku demikian). Bila nilai ada tetapi tipe tidak cocok (misalnya map menyimpan Long tetapi property dideklarasikan Int), runtime akan melemparkan ClassCastException.
Versi Read-Only dan Mutable #
Versi Map (read-only) hanya mendukung val. Bila kita perlu mengubah property dan ingin perubahan tercermin pada map, gunakan MutableMap bersama dengan var.
class PenggunaMutable(val data: MutableMap<String, Any?>) {
var nama: String by data
var email: String by data
}Saat pengguna.nama = "Dewi" dieksekusi, kompilator memanggil data.setValue(...) yang menulis data["nama"] = "Dewi". Karena itu, pemanggil yang masih memegang referensi data akan melihat perubahan tersebut — map berperilaku sebagai single source of truth.
Contoh Kode #
File map_backed_properties.kt memperagakan kedua versi:
// File: map_backed_properties.kt
// Mendemonstrasikan property yang didukung Map — umum untuk JSON parsing.
package com.example.mapback
// Property dipetakan ke key pada map via nama property.
class Pengguna(val data: Map<String, Any?>) {
val id: Long by data
val nama: String by data
val email: String by data
val umur: Int by data
}
// Versi mutable: pakai MutableMap, jadi property bisa ditulis.
class PenggunaMutable(val data: MutableMap<String, Any?>) {
var id: Long by data
var nama: String by data
var email: String by data
}
fun main() {
// Simulasi parsing JSON ke map.
val mapJson = mapOf<String, Any?>(
"id" to 101L,
"nama" to "Budi",
"email" to "[email protected]",
"umur" to 30
)
val pengguna = Pengguna(mapJson)
println("id: ${pengguna.id}")
println("nama: ${pengguna.nama}")
println("email: ${pengguna.email}")
println("umur: ${pengguna.umur}")
println()
// Versi mutable: ubah property langsung tercermin ke map.
val data = mutableMapOf<String, Any?>(
"id" to 202L,
"nama" to "Siti",
"email" to "[email protected]"
)
val pmut = PenggunaMutable(data)
println("Sebelum ubah: nama=${pmut.nama}")
pmut.nama = "Dewi"
pmut.email = "[email protected]"
println("Setelah ubah: nama=${pmut.nama}, email=${pmut.email}")
println()
println("Isi map setelah perubahan property:")
for ((k, v) in data) {
println(" $k = $v")
}
}Output yang Dihasilkan:
id: 101
nama: Budi
email: [email protected]
umur: 30
Sebelum ubah: nama=Siti
Setelah ubah: nama=Dewi, [email protected]
Isi map setelah perubahan property:
id = 202
nama = Dewi
email = [email protected]Mari kita bedah baris penting pada kode di atas:
- Baris 6-11 (
class Pengguna(val data: Map<String, Any?>)): konstruktor menyimpan referensidata. Empat propertyvalmemakaiby data— artinyapengguna.iddipetakan kedata["id"],pengguna.namakedata["nama"], dan seterusnya. Nama property menjadi key map secara otomatis. - Baris 14-18 (
class PenggunaMutable(val data: MutableMap<String, Any?>)): versi mutable. Property dideklarasikanvarsehingga kompilator memerlukansetValuepada delegate. Extension functionsetValuepadaMutableMapmemenuhi kontrak tersebut dan menulis key ke map. - Baris 22-27 (
mapOf(...)): simulasi payload JSON. Perhatikan tipe nilai berbeda-beda:Long,String,Int. KarenaMapmenyimpanAny?, tipe statis tidak menjamin keamanan — keamanan tipe hanya diperoleh pada saat akses property dilakukan, ketika delegate melakukan cast ke tipe yang dideklarasikan. - Baris 37-41 (
mutableMapOf(...)): versi mutable. Map ini diberikan kePenggunaMutable. Setelahpmut.nama = "Dewi"dieksekusi, key"nama"padadataditimpa menjadi"Dewi". Begitu jugaemail. - Baris 50-53 (iterasi
data): mencetak seluruh isi map. Output memperlihatkan bahwaidtetap202(tidak diubah),namaberubah menjadiDewi, danemailberubah menjadi[email protected]. Hal ini membuktikan bahwa map dan property benar-benar berbagi state yang sama — tidak ada backing field terpisah pada class.
Kapan Memakai Map-Backed #
Map-backed property paling berguna ketika data bersifat dinamis dan skema bisa berubah: payload JSON dari API eksternal, hasil query fleksibel, atau konfigurasi yang dimuat dari file. Pola ini menyembunyikan akses map berantai (data["nama"] as String) di balik property bertipe sehingga pemanggil mendapat autocompletion dan keamanan tipe pada level statis.
Referensi Lanjutan #
- Delegated properties — bagian map-backed property pada panduan resmi
- kotlin.properties package
— API reference paket
kotlin.propertiesuntuk delegate map
Selanjutnya #
Bila delegate standar tidak cukup, kita dapat menulis delegate sendiri. Chapter Custom Property Delegates akan membahas cara mengimplementasikan ReadOnlyProperty dan ReadWriteProperty untuk kebutuhan kustom seperti pembacaan environment variable atau transformasi nilai saat penugasan.