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.

kotlin Bentuk umum map-backed
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.

kotlin Versi mutable
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:

kotlin
// 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:

text
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:

  1. Baris 6-11 (class Pengguna(val data: Map<String, Any?>)): konstruktor menyimpan referensi data. Empat property val memakai by data — artinya pengguna.id dipetakan ke data["id"], pengguna.nama ke data["nama"], dan seterusnya. Nama property menjadi key map secara otomatis.
  2. Baris 14-18 (class PenggunaMutable(val data: MutableMap<String, Any?>)): versi mutable. Property dideklarasikan var sehingga kompilator memerlukan setValue pada delegate. Extension function setValue pada MutableMap memenuhi kontrak tersebut dan menulis key ke map.
  3. Baris 22-27 (mapOf(...)): simulasi payload JSON. Perhatikan tipe nilai berbeda-beda: Long, String, Int. Karena Map menyimpan Any?, tipe statis tidak menjamin keamanan — keamanan tipe hanya diperoleh pada saat akses property dilakukan, ketika delegate melakukan cast ke tipe yang dideklarasikan.
  4. Baris 37-41 (mutableMapOf(...)): versi mutable. Map ini diberikan ke PenggunaMutable. Setelah pmut.nama = "Dewi" dieksekusi, key "nama" pada data ditimpa menjadi "Dewi". Begitu juga email.
  5. Baris 50-53 (iterasi data): mencetak seluruh isi map. Output memperlihatkan bahwa id tetap 202 (tidak diubah), nama berubah menjadi Dewi, dan email berubah 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 #

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.