Convert #
dart:convert adalah library bawaan Dart untuk konversi data antara format yang berbeda — JSON, UTF-8, Base64, Latin1, ASCII, dan HTML escape. Lebih dari sekadar jsonDecode dan jsonEncode, library ini menyediakan abstraksi Codec dan Converter yang bisa dikomposisi secara fleksibel, mendukung streaming untuk data besar, dan bisa diperluas dengan codec kustom. Memahami dart:convert secara penuh membuka kemampuan untuk menangani konversi data yang kompleks dan efisien.
Gambaran dart:convert
#
flowchart LR
DC["dart:convert"] --> JSON["JsonCodec\njsonEncode / jsonDecode"]
DC --> UTF8["Utf8Codec\nutf8.encode / utf8.decode"]
DC --> B64["Base64Codec\nbase64Encode / base64Decode"]
DC --> LAT["Latin1Codec\nlatin1.encode / latin1.decode"]
DC --> ASCII["AsciiCodec\nascii.encode / ascii.decode"]
DC --> HTML["HtmlEscapeCodec\nhtmlEscape.convert"]
DC --> CHAIN["Codec Chaining\ncodec.fuse(otherCodec)"]JSON — JsonCodec
#
import 'dart:convert';
// Encode — Dart object → JSON string
final map = {
'nama': 'Budi',
'umur': 25,
'aktif': true,
'nilai': null,
'skor': [90, 85, 92],
'alamat': {'kota': 'Jakarta'},
};
final jsonString = jsonEncode(map);
print(jsonString);
// {"nama":"Budi","umur":25,"aktif":true,"nilai":null,"skor":[90,85,92],...}
// Decode — JSON string → Dart object
final decoded = jsonDecode(jsonString) as Map<String, dynamic>;
print(decoded['nama']); // 'Budi'
// Dengan indentasi (untuk debugging/logging)
final jsonRapi = JsonEncoder.withIndent(' ').convert(map);
print(jsonRapi);
// {
// "nama": "Budi",
// "umur": 25,
// ...
// }
// Alternatif API — menggunakan instance codec
final codec = json; // alias untuk JsonCodec()
print(codec.encode(map));
print(codec.decode(jsonString));
toEncodable — Encode Tipe Non-Standar
#
import 'dart:convert';
// Encode tipe yang tidak didukung JSON secara native
final data = {
'dibuat': DateTime.now(), // DateTime tidak bisa di-encode langsung
'status': StatusOrder.aktif, // Enum tidak bisa di-encode langsung
'url': Uri.parse('https://dart.dev'), // Uri tidak bisa di-encode langsung
};
// ANTI-PATTERN: jsonEncode langsung — throw JsonUnsupportedObjectError
// jsonEncode(data); // ✗ crash!
// BENAR: toEncodable untuk menangani tipe non-standar
final encoded = jsonEncode(data, toEncodable: (value) {
if (value is DateTime) return value.toUtc().toIso8601String();
if (value is Enum) return value.name;
if (value is Uri) return value.toString();
throw UnsupportedError('Tipe tidak didukung: ${value.runtimeType}');
});
print(encoded);
// {"dibuat":"2024-11-15T07:30:00.000Z","status":"aktif","url":"https://dart.dev"}
Streaming JSON — JsonEncoder dan JsonDecoder
#
Untuk JSON yang sangat besar, streaming lebih efisien dari string biasa:
import 'dart:convert';
import 'dart:io';
// Stream JSON ke file tanpa memuat seluruhnya ke memori
Future<void> tulisJsonKefile(String path, List<Map<String, dynamic>> data) async {
final file = File(path);
final sink = file.openWrite();
final encoder = JsonEncoder.withIndent(' ');
// Tulis sebagai JSON Lines (NDJSON) — satu objek per baris
for (final item in data) {
sink.writeln(encoder.convert(item));
}
await sink.flush();
await sink.close();
}
// Baca NDJSON dari file secara streaming
Future<void> bacaNdjson(String path) async {
final file = File(path);
await file
.openRead()
.transform(utf8.decoder)
.transform(const LineSplitter())
.map((baris) => jsonDecode(baris) as Map<String, dynamic>)
.forEach((obj) => print(obj['nama']));
}
UTF-8 — Utf8Codec
#
import 'dart:convert';
// Encode String → bytes (List<int>)
List<int> bytes = utf8.encode('Halo, Dunia! 🌍');
print(bytes.length); // lebih besar dari string.length karena emoji 4 bytes
// Decode bytes → String
String teks = utf8.decode(bytes);
print(teks); // 'Halo, Dunia! 🌍'
// allowMalformed: true — abaikan byte sequence yang tidak valid
String safe = utf8.decode(bytesRusak, allowMalformed: true);
// Streaming — encoder dan decoder sebagai StreamTransformer
import 'dart:io';
// Baca file sebagai Stream<String>
final stream = File('besar.txt')
.openRead()
.transform(utf8.decoder); // Stream<List<int>> → Stream<String>
// Tulis String sebagai bytes
final sink = File('output.txt').openWrite();
sink.add(utf8.encode('Konten file'));
// Encoder dan decoder sebagai objek terpisah
final encoder = utf8.encoder; // Converter<String, List<int>>
final decoder = utf8.decoder; // Converter<List<int>, String>
print(encoder.convert('test')); // [116, 101, 115, 116]
print(decoder.convert([116, 101, 115, 116])); // 'test'
Perbandingan Encoding #
import 'dart:convert';
const teks = 'Halo! Résumé café naïve';
// UTF-8 — mendukung semua Unicode
final utf8Bytes = utf8.encode(teks);
print('UTF-8: ${utf8Bytes.length} bytes');
// Latin-1 / ISO-8859-1 — hanya karakter Latin (code point 0-255)
final latin1Bytes = latin1.encode('Halo! Resume cafe naive'); // tanpa aksen
print('Latin-1: ${latin1Bytes.length} bytes');
// ASCII — hanya 7-bit ASCII (0-127)
try {
final asciiBytes = ascii.encode(teks); // ✗ throw jika ada karakter non-ASCII
} on ArgumentError catch (e) {
print('ASCII error: $e');
}
// ASCII dengan penanganan
final asciiSafe = ascii.encode('Hello World'); // ✓ hanya ASCII
Base64 — Base64Codec
#
Base64 mengubah data biner menjadi teks ASCII yang aman untuk transport (HTTP header, email, JSON):
import 'dart:convert';
// Encode bytes → Base64 string
final data = [72, 101, 108, 108, 111]; // 'Hello' dalam bytes
final b64 = base64Encode(data);
print(b64); // 'SGVsbG8='
// Decode Base64 string → bytes
final decoded = base64Decode(b64);
print(decoded); // [72, 101, 108, 108, 111]
print(utf8.decode(decoded)); // 'Hello'
// Round-trip String → Base64 → String
String teks = 'Data rahasia: [email protected]';
String encoded = base64Encode(utf8.encode(teks));
print(encoded); // 'RGF0YSByYWhhc2lhOiBidWRpQGV4YW1wbGUuY29t'
String restored = utf8.decode(base64Decode(encoded));
print(restored); // 'Data rahasia: [email protected]'
// Base64Url — versi aman untuk URL (menggunakan - dan _ bukan + dan /)
final urlSafe = base64Url.encode(data);
print(urlSafe); // tanpa karakter + dan /
// Base64 padding — beberapa implementasi tidak menyertakan padding (=)
String tanpaPadding = base64Encode(data).replaceAll('=', '');
// Tambahkan padding sebelum decode jika perlu
String denganPadding = tanpaPadding.padRight(
tanpaPadding.length + (4 - tanpaPadding.length % 4) % 4,
'=',
);
Use Case Base64 #
import 'dart:convert';
import 'dart:io';
// 1. HTTP Basic Authentication
String basicAuth(String username, String password) {
final credentials = '$username:$password';
return 'Basic ${base64Encode(utf8.encode(credentials))}';
}
print(basicAuth('admin', 'password'));
// 'Basic YWRtaW46cGFzc3dvcmQ='
// 2. Encode gambar untuk data URL
Future<String> imageToDataUrl(String imagePath) async {
final bytes = await File(imagePath).readAsBytes();
final b64 = base64Encode(bytes);
return 'data:image/png;base64,$b64';
}
// 3. JWT token (header.payload.signature — masing-masing Base64Url)
Map<String, dynamic> decodeJwtPayload(String token) {
final parts = token.split('.');
if (parts.length != 3) throw ArgumentError('Token JWT tidak valid');
// Tambahkan padding jika perlu
String padded = parts[1];
switch (padded.length % 4) {
case 2: padded += '=='; break;
case 3: padded += '='; break;
}
final decoded = utf8.decode(base64Url.decode(padded));
return jsonDecode(decoded) as Map<String, dynamic>;
}
HTML Escape — HtmlEscape
#
import 'dart:convert';
// HtmlEscape.convert — escape karakter HTML berbahaya
const htmlEscape = HtmlEscape();
print(htmlEscape.convert('<script>alert("XSS")</script>'));
// '<script>alert("XSS")</script>'
print(htmlEscape.convert('5 > 3 && 2 < 4'));
// '5 > 3 && 2 < 4'
// Mode escape yang berbeda
const modeAtribut = HtmlEscape(HtmlEscapeMode.attribute);
const modeTeks = HtmlEscape(HtmlEscapeMode.element);
const modeUrl = HtmlEscape(HtmlEscapeMode.unknown);
// Gunakan saat menyisipkan data ke HTML
String renderKartuPengguna(String nama, String bio) {
final escape = HtmlEscape();
return '''
<div class="kartu">
<h2>${escape.convert(nama)}</h2>
<p>${escape.convert(bio)}</p>
</div>
''';
}
// ANTI-PATTERN: menyisipkan input user langsung ke HTML — XSS vulnerability!
String html = '<p>Halo, $namaUser!</p>'; // ✗ jika namaUser = '<script>...</script>'
// BENAR: selalu escape input user
String html2 = '<p>Halo, ${htmlEscape.convert(namaUser)}!</p>'; // ✓
Codec Chaining — fuse
#
Salah satu fitur paling powerful dart:convert yang jarang diketahui: codec bisa di-compose dengan fuse():
import 'dart:convert';
// fuse — gabungkan dua codec menjadi satu
// String → UTF-8 bytes → Base64 string
final utf8ToBase64 = utf8.encoder.fuse(base64.encoder);
final stringKeBase64 = utf8ToBase64.convert('Halo, Dunia!');
print(stringKeBase64); // 'SGFsbywgRHVuaWEh'
// Balikkan: Base64 string → UTF-8 bytes → String
final base64ToUtf8 = base64.decoder.fuse(utf8.decoder);
final base64KeString = base64ToUtf8.convert('SGFsbywgRHVuaWEh');
print(base64KeString); // 'Halo, Dunia!'
// Chain JSON + UTF-8 — String JSON → bytes
final jsonToUtf8 = json.encoder.fuse(utf8.encoder);
final jsonBytes = jsonToUtf8.convert({'nama': 'Budi', 'umur': 25});
print(jsonBytes); // List<int> representasi JSON dalam UTF-8
// Decode balik
final utf8ToJson = utf8.decoder.fuse(json.decoder);
final objek = utf8ToJson.convert(jsonBytes);
print(objek); // {'nama': 'Budi', 'umur': 25}
// Streaming dengan codec chain
import 'dart:io';
// Baca file → decode UTF-8 → parse JSON lines
final stream = File('data.ndjson')
.openRead()
.transform(utf8.decoder) // bytes → String
.transform(const LineSplitter()) // String → per baris
.map(jsonDecode); // String → dynamic
Custom Codec #
Untuk format konversi yang tidak tersedia di dart:convert, buat codec sendiri:
import 'dart:convert';
// Codec kustom untuk format CSV sederhana
class CsvCodec extends Codec<List<List<String>>, String> {
const CsvCodec({this.separator = ','});
final String separator;
@override
Converter<List<List<String>>, String> get encoder => CsvEncoder(separator);
@override
Converter<String, List<List<String>>> get decoder => CsvDecoder(separator);
}
class CsvEncoder extends Converter<List<List<String>>, String> {
const CsvEncoder(this.separator);
final String separator;
@override
String convert(List<List<String>> input) {
return input
.map((row) => row
.map((cell) => cell.contains(separator)
? '"$cell"' // wrap dengan kutip jika mengandung separator
: cell)
.join(separator))
.join('\n');
}
}
class CsvDecoder extends Converter<String, List<List<String>>> {
const CsvDecoder(this.separator);
final String separator;
@override
List<List<String>> convert(String input) {
return input
.split('\n')
.where((baris) => baris.isNotEmpty)
.map((baris) => baris.split(separator))
.toList();
}
}
// Penggunaan
void main() {
const csv = CsvCodec();
final data = [
['nama', 'umur', 'kota'],
['Budi', '25', 'Jakarta'],
['Siti', '30', 'Bandung'],
];
final csvString = csv.encode(data);
print(csvString);
// nama,umur,kota
// Budi,25,Jakarta
// Siti,30,Bandung
final decoded = csv.decode(csvString);
print(decoded[1]); // ['Budi', '25', 'Jakarta']
}
Referensi Cepat #
| Codec | Encode | Decode | Kegunaan |
|---|---|---|---|
json | jsonEncode(obj) | jsonDecode(str) | JSON serialisasi |
utf8 | utf8.encode(str) | utf8.decode(bytes) | String ↔ bytes |
latin1 | latin1.encode(str) | latin1.decode(bytes) | ISO-8859-1 |
ascii | ascii.encode(str) | ascii.decode(bytes) | 7-bit ASCII |
base64 | base64Encode(bytes) | base64Decode(str) | Binary ↔ teks |
base64Url | base64Url.encode(bytes) | base64Url.decode(str) | Base64 URL-safe |
htmlEscape | htmlEscape.convert(str) | — | Mencegah XSS |
Ringkasan #
jsonEncode/jsonDecodeadalah fungsi top-level yang merupakan shortcut untukjson.encode/json.decode— keduanya ekuivalen.toEncodableparameter dijsonEncodememungkinkan encode tipe non-standar sepertiDateTime,Enum, danUri— selalu tangani ini daripada membiarkanJsonUnsupportedObjectError.utf8adalah codec default untuk semua I/O modern — HTTP, file, WebSocket, dan Socket semua menggunakan UTF-8. Gunakanutf8.encode/utf8.decodeuntuk konversi String ↔ bytes.base64vsbase64Url— gunakanbase64Urluntuk nilai yang akan dimasukkan ke URL atau JWT token karena tidak mengandung karakter+dan/yang harus di-encode dalam URL.fuse()untuk meng-compose codec —utf8.encoder.fuse(base64.encoder)menghasilkan converter langsung dari String ke Base64 tanpa variabel intermediate.HtmlEscapewajib digunakan saat menyisipkan data user ke HTML — mencegah XSS injection yang merupakan celah keamanan serius.- Streaming codec (
transform()) lebih efisien untuk file besar —openRead().transform(utf8.decoder)membaca file besar tanpa memuat seluruhnya ke memori.JsonEncoder.withIndent(' ')untuk output JSON yang mudah dibaca manusia — gunakan saat debugging atau membuat file konfigurasi.utf8.decoderdenganallowMalformed: trueuntuk menangani data biner yang mungkin rusak — tanpa flag ini, byte sequence UTF-8 yang tidak valid akan throw FormatException.- Custom Codec dengan extend
Codec<S, T>memungkinkan membuat format konversi kustom yang terintegrasi dengan sistem streaming dan fusedart:convert.