Sebagian besar aplikasi mengambil datanya dari API, dan data itu tiba sebagai teks JSON yang belum bisa ditampilkan. Topik ini membahas alur data dari hulu ke hilir, yaitu permintaan GET dengan package http, penguraian string JSON dengan dart:convert, dan pemodelan hasilnya sebagai kelas Dart. Satu program yang dapat dijalankan langsung disertakan, sehingga setiap tahap penguraian dapat diperiksa dari keluaran aslinya.
Package http dan permintaan GET#
Package http adalah library resmi Dart untuk mengirim permintaan HTTP dengan API yang ringkas. Instalasinya dilakukan sekali per proyek dengan perintah berikut.
# tidak-dijalankan
# menambahkan package http ke proyek
flutter pub add httpSetelah terpasang, permintaan GET ditulis dengan http.get yang menerima objek Uri dan mengembalikan Future<Response>. Pemanggilannya ditandai await sebagaimana pola async-await pada pembahasan Dart. Objek Response membawa dua hal yang paling sering digunakan, yaitu statusCode yang menyatakan hasil permintaan dan body yang berisi isi respons dalam bentuk String. Alamat yang diminta disebut endpoint, yakni satu alamat URL pada API yang melayani satu jenis data. Contoh berikut menggunakan endpoint publik JSON Placeholder yang memang dirancang untuk latihan.
// tidak-dijalankan
import 'package:http/http.dart' as http;
Future<void> ambilDaftarCatatan() async {
final respons = await http.get(
Uri.parse('https://jsonplaceholder.typicode.com/posts'),
);
if (respons.statusCode == 200) {
print('Panjang body: ${respons.body.length} karakter');
} else {
print('Permintaan gagal dengan kode ${respons.statusCode}');
}
}Pemeriksaan statusCode dilakukan sebelum isi respons diproses. Kode 200 berarti permintaan berhasil dan body memuat datanya, sedangkan kode lain, misalnya 404 atau 500, berarti isi body kemungkinan besar bukan data yang diharapkan. Karena body hanyalah teks, tahap berikutnya adalah menguraikannya menjadi struktur Dart.
Mengurai JSON dengan dart:convert#
JSON (JavaScript Object Notation) adalah format teks untuk pertukaran data yang menjadi bahasa paling umum antara aplikasi dan API. Library bawaan Dart untuk format ini adalah dart:convert, yang menyediakan jsonDecode dan jsonEncode. Fungsi jsonDecode menerima string JSON dan mengembalikan struktur Dart yang padanannya, yaitu Map<String, dynamic> untuk objek JSON dan List untuk array JSON. Proses dari teks menjadi objek disebut deserialisasi, sedangkan arah sebaliknya disebut serialisasi.
Alur ini dapat dijalankan utuh tanpa jaringan karena teks JSON dapat disimpan sebagai string literal. Program berikut memuat statistik nilai ujian satu kelas dalam string JSON, mengurainya dengan jsonDecode, memetakannya menjadi objek model, lalu mengolah statistiknya. Simpan sebagai nilai-ujian.dart, lalu jalankan dengan perintah dart nilai-ujian.dart.
// Mengurai JSON statistik nilai ujian dengan dart:convert, tanpa jaringan.
import 'dart:convert';
// String JSON lengkap, berperan sebagai isi respons yang sudah diterima.
const String jsonKelas = '''
[
{"nama": "Ali", "nilai": 88},
{"nama": "Budi", "nilai": 75},
{"nama": "Citra", "nilai": 92},
{"nama": "Dewi", "nilai": 68}
]
''';
// Kelas model untuk satu baris data nilai ujian.
class NilaiUjian {
final String nama;
final int nilai;
const NilaiUjian({required this.nama, required this.nilai});
// Factory constructor: membentuk objek dari satu Map hasil jsonDecode.
factory NilaiUjian.fromJson(Map<String, dynamic> json) {
return NilaiUjian(
nama: json['nama'] as String,
nilai: json['nilai'] as int,
);
}
// Arah sebaliknya: objek kembali menjadi Map yang siap di-jsonEncode.
Map<String, dynamic> toJson() => {'nama': nama, 'nilai': nilai};
// Nilai minimal tuntas untuk contoh ini adalah 75.
String get kategori => nilai >= 75 ? 'tuntas' : 'remedial';
}
void main() {
// jsonDecode mengubah string JSON menjadi struktur Dart.
final List<dynamic> data = jsonDecode(jsonKelas) as List<dynamic>;
// Setiap elemen array dipetakan menjadi objek model.
final daftar = data
.map((elemen) => NilaiUjian.fromJson(elemen as Map<String, dynamic>))
.toList();
print('Jumlah peserta: ${daftar.length}');
for (final ujian in daftar) {
print('${ujian.nama}: ${ujian.nilai} (${ujian.kategori})');
}
final total = daftar.map((u) => u.nilai).reduce((a, b) => a + b);
print('Rata-rata kelas: ${total / daftar.length}');
final tertinggi = daftar.reduce((a, b) => a.nilai >= b.nilai ? a : b);
print('Nilai tertinggi: ${tertinggi.nama} (${tertinggi.nilai})');
// jsonEncode mengubah objek Dart kembali menjadi string JSON.
final salinan = jsonEncode(daftar.map((u) => u.toJson()).toList());
print('JSON kembali: $salinan');
}Jumlah peserta: 4
Ali: 88 (tuntas)
Budi: 75 (tuntas)
Citra: 92 (tuntas)
Dewi: 68 (remedial)
Rata-rata kelas: 80.75
Nilai tertinggi: Citra (92)
JSON kembali: [{"nama":"Ali","nilai":88},{"nama":"Budi","nilai":75},{"nama":"Citra","nilai":92},{"nama":"Dewi","nilai":68}]Keluaran menunjukkan pengolahan atas objek model, bukan atas Map mentah. Empat baris pertama mencetak kategori kelulusan yang dihitung oleh getter pada kelas model, kemudian rata-rata dan nilai tertinggi dihitung dari daftar objek yang sudah bertipe. Baris terakhir adalah bukti arah sebaliknya, yang dibahas setelah ini.
Kelas model dan factory fromJson#
Kelas model adalah kelas Dart yang menampung satu baris data dengan tipe yang tepat, dalam hal ini NilaiUjian dengan nama berupa String dan nilai berupa int. Pada program, factory NilaiUjian.fromJson menerima satu Map<String, dynamic> hasil jsonDecode dan mengembalikan objek yang sudah terisi. Konstruktor factory adalah konstruktor yang mengembalikan instance yang sudah dibentuk, bukan sekadar mengisi field objek baru, dan pola ini lazim menjadi pintu masuk data eksternal.
Cara ini menggantikan akses json['nama'] yang tersebar di tampilan. Setelah data berpindah ke objek, salah ketik seperti ujian.nilaii tertangkap penganalisis Dart sebagai error, sedangkan salah menulis kunci pada Map baru terasa saat aplikasi berjalan. Getter kategori pada model memperlihatkan keuntungan lain, yaitu logika turunan seperti batas kelulusan cukup ditulis sekali di tempat data itu didefinisikan.
Pilihan factory bukan keharusan, tetapi memberi satu pintu bernama untuk konversi. Fungsi biasa dapat menghasilkan objek yang sama, sedangkan konstruktor factory membuat pemanggilannya terbaca seperti pembuatan objek biasa dan melekat pada kelasnya. Bentuk mana pun diterima, dan yang penting adalah seluruh pemetaan berada di satu tempat yang sama, bukan tersebar di kode tampilan.
Dari JSON array ke List model#
Array JSON diurai oleh jsonDecode menjadi List<dynamic>, sehingga tiap elemennya masih bertipe dynamic. Pada program, daftar model dibentuk dengan memetakan setiap elemen melalui NilaiUjian.fromJson setelah elemen tersebut di-cast ke Map<String, dynamic>, lalu hasil pemetaannya dikumpulkan dengan toList. Pola .map(...).toList() seperti ini berlaku untuk array JSON apa pun, dari daftar catatan sampai daftar statistik.
Tipe kembalian jsonDecode memang dynamic, sehingga cast pada tiap tahap bukan formalitas, melainkan pengecekan tipe yang sungguhan. Apabila satu elemen ternyata bukan Map<String, dynamic>, error terlempar tepat di baris cast tersebut, bukan jauh di dalam tampilan, sehingga penyebabnya mudah dilacak. Inilah alasan pemetaan diserahkan kepada kelas model alih-alih mengakses Map mentah di banyak tempat.
Apabila data JSON bersarang, misalnya data satu kelas yang memuat daftar pesertanya, fromJson pada model terluar dapat memanggil fromJson model di dalamnya untuk setiap elemen bertingkat. Prinsipnya tetap sama, yaitu setiap tingkat bersarang dipetakan menjadi model pada tingkatnya masing-masing, sehingga pohon data yang dalam tetap berubah menjadi objek bertipe.
jsonEncode untuk arah sebaliknya#
Arah sebaliknya ditempuh oleh toJson dan jsonEncode. Metode toJson pada NilaiUjian mengembalikan Map<String, dynamic> yang berisi nilai-nilai objek, sedangkan jsonEncode mengubah struktur tersebut kembali menjadi string JSON. Pada program, baris JSON kembali mencetak hasil jsonEncode atas seluruh daftar model, dan formatnya rapat tanpa spasi karena jsonEncode menulis notasi yang paling ringkas.
Format rapat itu sah sebagai JSON tetapi kurang nyaman dibaca manusia. Kebutuhan tampilan yang berindentasi, misalnya untuk berkas konfigurasi atau log, dapat dipenuhi dengan JsonEncoder.withIndent dari library yang sama. Untuk pengiriman ke API, bentuk rapat justru yang diinginkan karena ukurannya paling kecil.
Kemampuan ini dibutuhkan ketika aplikasi mengirim data ke API, misalnya permintaan POST yang body-nya berupa string JSON hasil jsonEncode. Kebutuhan lain muncul pada penyimpanan lokal, karena banyak tempat penyimpanan sederhana hanya menerima string. Kebutuhan itu dibahas lebih lanjut pada topik persistensi lokal.
Kesalahan umum#
- Mengurai
bodytanpa memeriksastatusCode. Respons gagal sering berupa halaman HTML atau pesan error yang bukan JSON, sehinggajsonDecodemelemparFormatException. Perbaikannya, periksarespons.statusCode == 200sebelum mengurai. - Menganggap hasil
jsonDecodesudah siap ditampilkan. Hasilnya masihdynamicberupaMapdanListmentah, sehingga salah ketik kunci hanya terasa saat aplikasi berjalan. Perbaikannya, petakan hasilnya ke kelas model melaluifromJson. - Memaksakan tipe angka yang berbeda dari JSON. Nilai
88.0pada JSON diurai sebagaidouble, sedangkan model yang memintaintmelemparTypeErrorsaatas intdijalankan. Perbaikannya, samakan tipe model dengan isi JSON, atau gunakannumlalu konversi secara eksplisit. - Melupakan
awaitpadahttp.get. Tanpaawait, variabel berisiFuture<Response>, sehingga pembacaanstatusCodepada variabel tersebut ditolak penganalisis Dart. Perbaikannya, tandaiawaitpada setiap permintaan yang hasilnya langsung digunakan.
Ringkasan#
- Package
httpmengirim permintaan GET dan mengembalikanResponseberisistatusCodesertabodyberupaString. jsonDecodemengubah string JSON menjadi struktur Dart, sedangkanjsonEncodemengubah objek Dart kembali menjadi string JSON.- Kelas model dengan
factory fromJsondantoJsonmemindahkan data mentah menjadi objek bertipe yang aman diakses. - Array JSON menjadi
Listmodel melalui pemetaan.map(...).toList()atas tiap elemennya.
Referensi#
- http — Dart package
— Halaman resmi package
httpbeserta contoh permintaannya. - dart:convert — Dart
— Dokumentasi resmi
jsonDecodedanjsonEncode. - JSON and serialization — Flutter — Panduan resmi penguraian JSON dan serialisasi di Flutter.
- http API docs — Dart package
— Referensi API kelas
Responsedan metodeget.