JSON to Dart
Buat kelas Dart dari JSON dengan fromJson dan toJson
Input JSON
Output Dart
Apa itu Konversi JSON ke Dart?
Konversi JSON ke Dart mengambil objek JSON mentah dan menghasilkan definisi kelas Dart dengan field bertipe, konstruktor bernama, factory fromJson, dan metode toJson. Dart tidak memiliki reflection di Flutter (dart:mirrors dinonaktifkan), sehingga Anda tidak dapat melakukan deserialisasi JSON ke objek bertipe tanpa menulis kode pemetaan secara eksplisit. Setiap respons REST API, dokumen Firebase, atau payload konfigurasi memerlukan kelas model Dart yang sesuai sebelum Anda dapat mengakses field-nya dengan type safety.
Kelas model Dart standar untuk JSON mendeklarasikan field final untuk setiap kunci, konstruktor dengan parameter bernama (menggunakan kata kunci required untuk field non-nullable), factory constructor bernama fromJson yang membaca dari Map String ke dynamic, dan metode toJson yang mengembalikan Map String ke dynamic. Objek JSON bersarang menjadi kelas terpisah. Array menjadi field List bertipe. Nilai JSON nullable menggunakan sintaks null-safety Dart dengan sufiks ? pada tipenya.
Menulis kelas model ini secara manual berarti membaca setiap kunci JSON, menentukan tipe Dart-nya, membuat cast fromJson untuk setiap field (termasuk pemetaan list dengan .map().toList()), membangun map literal toJson, dan mengulangi proses untuk setiap objek bersarang. Untuk objek JSON dengan 12 field dan 2 objek bersarang, itu berarti 3 kelas, 6 baris factory, dan puluhan ekspresi cast. Sebuah konverter menghasilkan semua ini dalam milidetik dari satu kali paste.
Mengapa Menggunakan Konverter JSON ke Dart?
Membuat kelas model Dart dari JSON secara manual melibatkan pembacaan nama field, menebak tipe dari nilai sampel, menulis cast fromJson dengan penanganan null yang benar, dan mengulangi proses untuk objek bersarang. Ketika bentuk API berubah, setiap pembaruan field menyentuh konstruktor, fromJson, dan toJson sekaligus. Sebuah konverter menghilangkan pekerjaan berulang tersebut.
Kasus Penggunaan JSON ke Dart
Pemetaan Tipe JSON ke Dart
Setiap nilai JSON dipetakan ke tipe Dart yang spesifik. Tabel di bawah menunjukkan cara konverter menerjemahkan setiap tipe JSON. Kolom Alternatif menampilkan tipe yang digunakan dalam skenario pemetaan yang kurang umum atau manual.
| Tipe JSON | Contoh | Tipe Dart | Alternatif |
|---|---|---|---|
| string | "hello" | String | String |
| number (integer) | 42 | int | int |
| number (float) | 3.14 | double | double |
| boolean | true | bool | bool |
| null | null | dynamic | Null / dynamic |
| object | {"k": "v"} | NestedClass | Map<String, dynamic> |
| array of strings | ["a", "b"] | List<String> | List<String> |
| array of objects | [{"id": 1}] | List<Item> | List<Item> |
| mixed array | [1, "a"] | List<dynamic> | List<dynamic> |
Pendekatan Serialisasi JSON di Dart
Dart dan Flutter menawarkan beberapa cara untuk menangani serialisasi JSON. fromJson/toJson manual adalah pendekatan paling sederhana dan tidak memerlukan pembuatan kode. Untuk proyek yang lebih besar, solusi berbasis build_runner seperti json_serializable dan freezed menghasilkan boilerplate pada waktu kompilasi, mengurangi kesalahan dan menjaga model tetap konsisten dengan kontrak JSON.
| Pendekatan | Deskripsi | Sumber |
|---|---|---|
| json_serializable | Code-generation-based JSON serialization. Generates .g.dart files at build time via build_runner. Type-safe and compile-time verified. | Official (Google) |
| freezed | Generates immutable data classes with copyWith, fromJson/toJson, equality, and pattern matching support. Built on top of json_serializable. | Community (rrousselGit) |
| built_value | Generates immutable value types with serialization. Enforces immutability patterns. Used in larger codebases with strict data modeling. | |
| dart_mappable | Annotation-based mapper that generates fromJson, toJson, copyWith, and equality. Simpler setup than freezed with similar features. | Community |
| Manual fromJson/toJson | Hand-written factory constructors and toJson methods. No code generation needed. Full control over the mapping logic. | Built-in Dart |
Manual vs json_serializable vs freezed
Dart memiliki tiga pendekatan umum untuk kelas model JSON. fromJson/toJson manual tidak memerlukan dependensi. json_serializable mengotomatiskan kode pemetaan. freezed menambahkan immutabilitas, copyWith, dan pattern matching di atas json_serializable.
Contoh Kode
Contoh-contoh ini menunjukkan cara menggunakan kelas Dart yang dihasilkan untuk deserialisasi JSON, cara menyiapkan json_serializable dengan build_runner, dan cara menghasilkan kelas Dart secara programatik dari JavaScript dan Python.
import 'dart:convert';
class User {
final int id;
final String name;
final String email;
final bool active;
final Address address;
final List<String> tags;
User({
required this.id,
required this.name,
required this.email,
required this.active,
required this.address,
required this.tags,
});
factory User.fromJson(Map<String, dynamic> json) => User(
id: json['id'] as int,
name: json['name'] as String,
email: json['email'] as String,
active: json['active'] as bool,
address: Address.fromJson(json['address'] as Map<String, dynamic>),
tags: (json['tags'] as List<dynamic>).map((e) => e as String).toList(),
);
Map<String, dynamic> toJson() => {
'id': id,
'name': name,
'email': email,
'active': active,
'address': address.toJson(),
'tags': tags,
};
}
class Address {
final String street;
final String city;
final String zip;
Address({required this.street, required this.city, required this.zip});
factory Address.fromJson(Map<String, dynamic> json) => Address(
street: json['street'] as String,
city: json['city'] as String,
zip: json['zip'] as String,
);
Map<String, dynamic> toJson() => {'street': street, 'city': city, 'zip': zip};
}
void main() {
final jsonStr = '{"id":1,"name":"Alice","email":"alice@example.com","active":true,"address":{"street":"123 Main","city":"Springfield","zip":"12345"},"tags":["admin","user"]}';
final user = User.fromJson(jsonDecode(jsonStr));
print(user.name); // -> Alice
print(jsonEncode(user.toJson())); // -> round-trip back to JSON
}// pubspec.yaml dependencies:
// json_annotation: ^4.8.0
// dev_dependencies:
// build_runner: ^2.4.0
// json_serializable: ^6.7.0
import 'package:json_annotation/json_annotation.dart';
part 'user.g.dart'; // generated by: dart run build_runner build
@JsonSerializable()
class User {
final int id;
final String name;
final String email;
@JsonKey(name: 'is_active')
final bool isActive;
final List<String> tags;
User({
required this.id,
required this.name,
required this.email,
required this.isActive,
required this.tags,
});
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
Map<String, dynamic> toJson() => _$UserToJson(this);
}
// Run code generation:
// dart run build_runner build --delete-conflicting-outputsfunction jsonToDart(obj, name = "Root") {
const classes = [];
function infer(val, fieldName) {
if (val === null) return "dynamic";
if (typeof val === "string") return "String";
if (typeof val === "number") return Number.isInteger(val) ? "int" : "double";
if (typeof val === "boolean") return "bool";
if (Array.isArray(val)) {
const first = val.find(v => v !== null);
if (!first) return "List<dynamic>";
return `List<${infer(first, fieldName)}>`;
}
if (typeof val === "object") {
const cls = fieldName.charAt(0).toUpperCase() + fieldName.slice(1);
build(val, cls);
return cls;
}
return "dynamic";
}
function build(obj, cls) {
const fields = Object.entries(obj).map(([k, v]) =>
` final ${infer(v, k)} ${k};`
);
classes.push(`class ${cls} {\n${fields.join("\n")}\n}`);
}
build(obj, name);
return classes.join("\n\n");
}
console.log(jsonToDart({ id: 1, name: "Alice", scores: [98, 85] }, "User"));
// class User {
// final int id;
// final String name;
// final List<int> scores;
// }import json
def json_to_dart(obj: dict, class_name: str = "Root") -> str:
classes = []
def infer(val, name):
if val is None:
return "dynamic"
if isinstance(val, bool):
return "bool"
if isinstance(val, int):
return "int"
if isinstance(val, float):
return "double"
if isinstance(val, str):
return "String"
if isinstance(val, list):
if not val:
return "List<dynamic>"
return f"List<{infer(val[0], name)}>"
if isinstance(val, dict):
cls = name[0].upper() + name[1:]
build(val, cls)
return cls
return "dynamic"
def build(obj, cls):
fields = [f" final {infer(v, k)} {k};" for k, v in obj.items()]
classes.append(f"class {cls} {{\n" + "\n".join(fields) + "\n}")
build(obj, class_name)
return "\n\n".join(classes)
data = json.loads('{"id": 1, "name": "Alice", "active": true}')
print(json_to_dart(data, "User"))
# class User {
# final int id;
# final String name;
# final bool active;
# }