JSON adalah format teks yang paling lazim dipakai untuk pertukaran data antar aplikasi, dan Groovy menyediakan seluruh kelasnya di paket groovy.json. Topik ini menelusuri perjalanan data bolak-balik, yaitu mengurai teks JSON menjadi map dan list dengan JsonSlurper, membedakan perilaku pengurai ketat dan longgar, menyusun JSON kembali dengan JsonOutput dan JsonBuilder, serta mengonversi hasilnya menjadi objek bertipe yang disebut POGO (plain old Groovy object). Contoh yang dipakai meliputi statistik nilai ujian, catatan sensor suhu, dan daftar belanja pribadi. Setelah membacanya, dokumen JSON dapat diurai, ditelusuri, dibangkitkan dari kode Groovy, dan dikonversi bolak-balik dengan keluaran yang dapat direproduksi sendiri.
Mengurai JSON dengan JsonSlurper#
JsonSlurper mengurai teks JSON menjadi struktur data Groovy murni, yaitu objek JSON menjadi Map, array menjadi List, angka bulat menjadi Integer, angka pecahan menjadi BigDecimal, dan string menjadi String. Karena seluruh kelas JSON berada di paket groovy.json, setiap skrip wajib mengimpornya secara eksplisit, dan kelas yang tidak diimpor tidak dikenali saat kompilasi. Metode parseText menerima String berisi JSON dan mengembalikan akar dokumennya. Akar itu ditelusuri dengan notasi titik dan indeks sebagaimana map dan list Groovy biasa, termasuk struktur bersarang berupa list berisi map.
// baca-nilai-ujian.groovy: mengurai JSON dan menelusuri hasilnya
import groovy.json.JsonSlurper
def teks = '''{
"mataKuliah": "Aljabar Dasar",
"tahun": 2026,
"peserta": [
{"nama": "Ali", "nilai": 85},
{"nama": "Budi", "nilai": 78},
{"nama": "Citra", "nilai": 91},
{"nama": "Dewi", "nilai": 70},
{"nama": "Eko", "nilai": 88}
]
}'''
def rekap = new JsonSlurper().parseText(teks)
println "Mata kuliah : ${rekap.mataKuliah}"
println "Tahun : ${rekap.tahun}"
println "Jumlah : ${rekap.peserta.size()} peserta"
println "Peserta [2] : ${rekap.peserta[2].nama} (${rekap.peserta[2].nilai})"
// elvis: nilai bawaan bila kunci tidak ada pada dokumen
println "Catatan : ${rekap.catatan ?: '(tanpa catatan)'}"
// findAll menyaring list, operator spread mengambil satu properti
def unggul = rekap.peserta.findAll { it.nilai >= 80 }*.nama
println "Nilai >= 80 : ${unggul}"
def rataRata = rekap.peserta*.nilai.sum() / rekap.peserta.size()
println "Rata-rata : ${rataRata}"Mata kuliah : Aljabar Dasar
Tahun : 2026
Jumlah : 5 peserta
Peserta [2] : Citra (91)
Catatan : (tanpa catatan)
Nilai >= 80 : [Ali, Citra, Eko]
Rata-rata : 82.4Keluaran menelusuri navigasi dari yang paling sederhana hingga yang bersyarat. Ekspresi rekap.peserta[2].nama menjangkau elemen ketiga list peserta, sedangkan rekap.catatan ?: '(tanpa catatan)' adalah operator elvis yang memberi nilai bawaan ketika kunci catatan tidak ada pada dokumen. Metode findAll menyaring list menurut predikat closure, dan operator spread (*.) mengambil satu properti dari setiap elemen, misalnya *.nama untuk mengumpulkan deret nama dari sekumpulan map. Perhitungan rata-rata pun selesai dalam satu baris karena sum dan size bekerja langsung pada list angka hasil spread, dan seluruh nilai tersebut diperoleh tanpa konversi tipe tambahan.
Membaca berkas JSON#
Untuk data yang tersimpan sebagai berkas, JsonSlurper menyediakan metode parse yang menerima objek File langsung, sehingga isi berkas tidak perlu dibaca menjadi String terlebih dahulu. Varian lain menerima Reader, InputStream, Path, hingga URL, dan semuanya mengembalikan struktur yang sama dengan parseText. Skrip berikut menyiapkan berkas catatan sensor suhu dengan metode write GDK, lalu mengurainya dan menghitung statistik sederhana dari hasil pengukurannya.
// baca-catatan-suhu.groovy: menulis lalu membaca berkas JSON
import groovy.json.JsonSlurper
// berkas contoh disiapkan skrip sendiri, isi tetap sama di setiap eksekusi
def berkas = new File('catatan-sensor.json')
berkas.write('''{
"lokasi": "ruang peralatan",
"hasil": [
{"waktu": "07.00", "suhu": 24.5},
{"waktu": "13.00", "suhu": 27.0},
{"waktu": "17.00", "suhu": 25.0}
]
}''')
def catatan = new JsonSlurper().parse(berkas)
println "Lokasi : ${catatan.lokasi}"
println "Jumlah hasil: ${catatan.hasil.size()} pengukuran"
def suhu = catatan.hasil*.suhu
println "Tertinggi : ${suhu.max()} derajat"
println "Rata-rata : ${suhu.sum() / suhu.size()} derajat"Lokasi : ruang peralatan
Jumlah hasil: 3 pengukuran
Tertinggi : 27.0 derajat
Rata-rata : 25.5 derajatMetode parse menerima berkas dan mengembalikan map akarnya, sehingga navigasinya sama persis dengan hasil parseText. Ekspresi catatan.hasil*.suhu mengumpulkan seluruh nilai suhu menjadi satu list, dan metode max serta sum langsung bekerja di atasnya. Angka pecahan seperti 24.5 tetap menjadi BigDecimal pada hasil penguraian, sehingga rata-rata 25.5 muncul tanpa koreksi tipe apa pun. Pola menulis berkas contoh di awal skrip membuatnya dapat dijalankan berulang kali dengan keluaran yang sama, karena write selalu menimpa isi berkas dengan data yang tetap. Untuk berkas hasil kerja program lain, tahap penulisan itu cukup diganti pembacaan langsung.
Tiga pengurai, tiga perilaku#
Groovy memuat beberapa implementasi pengurai di dalam JsonSlurper, dan pilihannya menentukan seberapa longgar teks masukan diterima serta bagaimana hasilnya disusun. Pengurai default, JsonParserCharArray, mengikuti tata bahasa JSON standar secara ketat dan menyimpan objek dalam LazyMap, kelas internal yang mempertahankan urutan kunci persis seperti kemunculannya pada dokumen. Kelas JsonSlurperClassic yang lebih tua masih tersedia pada Groovy 5.1.3 tanpa penanda @Deprecated, dan keunikannya adalah hasil berupa HashMap biasa yang dapat langsung diserialisasi. Pilihan ketiga adalah parser LAX yang menerima teks di luar standar.
| Pengurai | Ciri utama |
|---|---|
default (JsonParserCharArray) | ketat mengikuti standar JSON, urutan kunci mengikuti dokumen |
| LAX | menerima kunci tanpa kutip dan komentar, mengonversi tanggal ISO-8601 penuh, urutan kunci tidak dijamin |
JsonSlurperClassic | hasil berupa HashMap yang dapat langsung diserialisasi |
import groovy.json.JsonSlurper
import groovy.json.JsonParserType
import groovy.json.JsonSlurperClassic
// parser default mempertahankan urutan kunci sesuai dokumen
def ketat = new JsonSlurper().parseText('{"c": 3, "a": 1, "b": 2}')
println "Urutan kunci : ${ketat.keySet()}"
// parser LAX menerima kunci tanpa kutip dan komentar
def lax = new JsonSlurper(type: JsonParserType.LAX)
def longgar = lax.parseText('{ /* catatan */ nama: "Ali", nilai: 85 }')
println "Kunci longgar: ${longgar.nama}, ${longgar.nilai}"
// JsonSlurperClassic menghasilkan HashMap yang bisa diserialisasi
def klasik = new JsonSlurperClassic().parseText('{"nama": "Budi", "nilai": 78}')
println "Kelas hasil : ${klasik.getClass().simpleName}"Urutan kunci : [c, a, b]
Kunci longgar: Ali, 85
Kelas hasil : HashMapKeluaran memperlihatkan ketiganya secara berurutan. Dokumen dengan kunci c, a, b diurai parser default dan keySet mengembalikan urutan persis sebagaimana ditulis, sehingga navigasi berbasis urutan dapat dipercaya pada pengurai ini. Parser LAX menerima dokumen yang memuat komentar dan kunci tanpa kutip, tetapi urutan kunci hasilnya tidak dijamin, sehingga nilai selalu dibaca lewat kuncinya dan bukan lewat posisi. Pengurai LAX juga mengonversi string tanggal ISO-8601 lengkap dengan jamnya menjadi java.util.Date, sedangkan parser default membiarkannya sebagai String. Hasil JsonSlurperClassic sendiri diperiksa lewat getClass lalu terbukti HashMap.
JsonOutput dan JsonBuilder#
Kelompok JsonOutput dan JsonBuilder menutup arah sebaliknya, yaitu menyusun objek Groovy menjadi teks JSON. JsonOutput menyediakan metode statis toJson yang menerima map, list, POGO, hingga struktur campuran, dan mengembalikan String JSON satu baris tanpa spasi. Metode statis prettyPrint menerima String JSON apa pun, bukan hanya hasil toJson, lalu mengembalikannya dalam bentuk terformat dengan indentasi empat spasi. Urutan kunci map mengikuti urutan penulisannya, sehingga map literal Groovy yang tersusun rapi menghasilkan JSON yang tersusun rapi pula.
import groovy.json.JsonOutput
def indeks = [mataKuliah: 'Aljabar Dasar', semester: 2, nilai: ['A', 'B+', 'A']]
println JsonOutput.toJson(indeks)
println '---'
println JsonOutput.prettyPrint(JsonOutput.toJson(indeks)){"mataKuliah":"Aljabar Dasar","semester":2,"nilai":["A","B+","A"]}
---
{
"mataKuliah": "Aljabar Dasar",
"semester": 2,
"nilai": [
"A",
"B+",
"A"
]
}JsonBuilder menawarkan gaya lain berbentuk DSL, yaitu kode yang dibaca seperti struktur datanya. Setiap pemanggilan metode di dalam closure menjadi sebuah kunci dengan argumennya sebagai nilai, dan closure bersarang menjadi objek bersarang. Untuk membangun array berisi objek, builder menerima sepasang list dan closure yang diproses untuk setiap elemen list. Hasil akhirnya dibaca kembali melalui properti content atau dirangkai menjadi String dengan toString dan toPrettyString.
// bangun-daftar-belanja.groovy: menyusun JSON dengan JsonBuilder
import groovy.json.JsonBuilder
def daftarBelanja = [
[nama: 'beras', jumlah: 1, harga: 68000],
[nama: 'telur', jumlah: 2, harga: 29000]
]
def belanja = new JsonBuilder()
belanja {
bulan 'Februari'
anggaranMaksimum 150000
// list dan closure: setiap elemen list diproses closure menjadi objek
butir(daftarBelanja, { b ->
nama b.nama
jumlah b.jumlah
harga b.harga
})
}
println belanja.toPrettyString()
println '---'
println "Butir pertama : ${belanja.content.butir[0].nama}"
println "Butir terakhir: ${belanja.content.butir[-1].harga}"{
"bulan": "Februari",
"anggaranMaksimum": 150000,
"butir": [
{
"nama": "beras",
"jumlah": 1,
"harga": 68000
},
{
"nama": "telur",
"jumlah": 2,
"harga": 29000
}
]
}
---
Butir pertama : beras
Butir terakhir: 29000Keluaran menunjukkan struktur tiga tingkat yang tersusun dari pemanggilan bersarang di dalam closure. Kunci butir berisi array dua objek yang masing-masing dirangkai closure dari satu elemen daftarBelanja, dan nilai anggaranMaksimum tetap berupa angka karena argumennya Integer. Navigasi belanja.content.butir[0] bekerja karena content adalah map Groovy biasa, sehingga JSON yang baru dibangun dapat langsung diperiksa tanpa diurai ulang. Indeks negatif -1 mengambil elemen terakhir, memanfaatkan dukungan indeks bawaan list Groovy.
Bolak-balik dengan POGO#
POGO adalah kelas Groovy yang propertinya dideklarasikan bertipe, dan kelas semacam ini menjadi wadah alami untuk data JSON yang strukturnya sudah diketahui sejak awal. Objek POGO dibuat dari map melalui constructor map, yaitu bentuk pemanggilan new dengan pasangan kunci dan nilai, atau melalui operator as yang mengonversi map menjadi objek. Konversi dengan as juga berlaku pada hasil penguraian JsonSlurper, sehingga map hasil parse menjadi objek bertipe dalam satu ekspresi, dan objek bersarang di dalamnya dikonversi mengikuti tipe propertinya.
import groovy.json.JsonOutput
import groovy.json.JsonSlurper
class Peserta {
String nama
Integer nilai
String catatan
}
// constructor map dan operator as membuat POGO dari data
def a = new Peserta(nama: 'Ali', nilai: 85)
def b = [nama: 'Budi', nilai: 78, catatan: 'naik dua peringkat'] as Peserta
println JsonOutput.toJson(a)
println JsonOutput.toJson(b)
// hasil parse dikonversi menjadi POGO dengan operator as
def json = '{"nama":"Citra","nilai":91,"catatan":null}'
def p = new JsonSlurper().parseText(json) as Peserta
println "POGO: ${p.nama}, ${p.nilai}, catatan=${p.catatan}"
// toJson pada POGO: urutan deklarasi dipertahankan, properti null ikut
println JsonOutput.toJson(p)
// list of maps menjadi list POGO melalui collect
def daftar = new JsonSlurper().parseText('[{"nama":"Dewi","nilai":70},{"nama":"Eko","nilai":88}]')
def kelas = daftar.collect { it as Peserta }
println "Jumlah POGO : ${kelas.size()}, nilai ${kelas*.nilai}"{"nama":"Ali","nilai":85,"catatan":null}
{"nama":"Budi","nilai":78,"catatan":"naik dua peringkat"}
POGO: Citra, 91, catatan=null
{"nama":"Citra","nilai":91,"catatan":null}
Jumlah POGO : 2, nilai [70, 88]Keluaran memperlihatkan dua sifat penting toJson pada POGO. Urutan kuncinya mengikuti urutan deklarasi properti pada kelas, dan properti yang masih null tetap dirangkai sebagai null, bukan dibuang, sehingga bentuk JSON yang dihasilkan stabil dan mudah diprediksi pembaca lain. Baris terakhir mengumpulkan list hasil parse menjadi list POGO melalui collect, sehingga seluruh elemen array JSON dikonversi sekaligus. Kombinasi JsonSlurper, operator as, dan JsonOutput dengan demikian menutup siklus bolak-balik dari teks JSON ke objek bertipe dan kembali menjadi teks.
Kesalahan umum#
Kesalahan pertama adalah mengurai JSON yang tidak lengkap. Teks yang terpotong di tengah, misalnya karena respons jaringan berhenti atau berkas belum selesai ditulis, membuat parser kehabisan token penutup dan melempar JsonException, yaitu exception khusus milik paket groovy.json. Penanganannya memakai try dan catch yang menangkap JsonException secara spesifik, sehingga jenis error lain tetap menyebar dan tidak tersamarkan.
import groovy.json.JsonSlurper
import groovy.json.JsonException
try {
new JsonSlurper().parseText('{"nama": "Ali"')
} catch (JsonException e) {
println "Gagal: ${e.getClass().simpleName}"
}Gagal: JsonExceptionKesalahan kedua adalah memakai kunci JSON yang tidak cocok dengan properti POGO, sering kali akibat salah ketik seperti niali untuk nilai. Constructor map gagal cepat dan melempar MissingPropertyException pada kunci pertama yang tidak dikenalnya. Operator as berperilaku sebaliknya, yaitu konversi tetap berhasil tanpa error, tetapi seluruh properti objek dibiarkan null, termasuk kunci yang sebenarnya cocok. Dengan demikian, data yang tampak sah di permukaan kosong di dalamnya, dan masalahnya baru muncul ketika properti kosong itu dibaca di bagian lain program.
import groovy.json.JsonOutput
class Peserta {
String nama
Integer nilai
}
// constructor map gagal cepat pada kunci yang tidak cocok
try {
new Peserta(nama: 'Ali', niali: 85)
} catch (MissingPropertyException e) {
println "Gagal: ${e.getClass().simpleName}"
}
// operator as diam-diam menghasilkan POGO tanpa properti terisi
def p = [nama: 'Ali', niali: 85] as Peserta
println "Hasil as: ${JsonOutput.toJson(p)}"Gagal: MissingPropertyException
Hasil as: {"nama":null,"nilai":null}Kebiasaan yang mencegah keduanya adalah menguji hasil konversi sebelum dipakai, misalnya mencetak JsonOutput.toJson dari objek yang baru dikonversi dan memastikan propertinya terisi. Bila kegagalan cepat lebih diinginkan daripada objek kosong, gunakan constructor map yang melempar MissingPropertyException sejak kunci pertama yang salah. Kedua jebakan ini muncul saat data berpindah bentuk, sehingga uji bolak-balik kecil, yaitu mengurai lalu merangkai ulang dan membandingkan hasilnya, sudah menyaring sebagian besar kasusnya sejak awal.
Ringkasan#
JsonSlurper.parseTextdanparsemengubah JSON menjadi map dan list Groovy murni, dan hasilnya ditelusuri dengan indeks, notasi titik, operator elvis,findAll, serta operator spread.- Pengurai default ketat dan mempertahankan urutan kunci dokumen, LAX menerima kunci tanpa kutip dan komentar tetapi tidak menjamin urutan kunci, sedangkan
JsonSlurperClassicmenghasilkanHashMap. JsonOutput.toJsonmerangkai objek Groovy menjadi JSON satu baris danprettyPrintmemformatnya, sedangkanJsonBuildermenyusun JSON lewat closure DSL, termasuk array berisi objek dari pasangan list dan closure.- POGO dibuat dari map dengan constructor map atau operator
as, dantoJsonpada POGO mengikuti urutan deklarasi properti serta tetap merangkai properti null.
Referensi#
- Parsing and producing JSON — Apache Groovy
— dokumentasi resmi yang mencakup tabel konversi tipe JSON, varian parser, konversi tanggal, dan koersi bertipe dengan
as. - JsonSlurper — Apache Groovy
— javadoc metode
parseuntukFile,Path,Reader,InputStream,URL, sertaparseText. - JsonOutput — Apache Groovy
— javadoc
toJsondanprettyPrintbeserta opsi penyesuaian lewatJsonGenerator. - JsonBuilder — Apache Groovy
— javadoc DSL builder, termasuk
calluntuk list dan closure sertatoPrettyString.