Fundamental Parsing JSON in Flutter
How to parsing JSON in Flutter
Pengenalan
Sama seperti bahasa pemrograman pada umumnya pasti ada yang namanya pengolahan data yang berbentuk json termasuklah itu di Flutter. Sebelum kita mempelajari bagaimana melakukan Rest API Call di Flutter alangkah lebih baiknya kita memahami penggunaan dasar json dimana, pada artikel ini kita fokus tentang bagaimana kita melakukan parsing json secara manual.
Persiapan
Sebelum kita masuk ke pengolahan json silakan kita buat terlebih dahulu UI main.dart-nya seperti berikut.
import 'package:flutter/material.dart';void main() => runApp(MaterialApp(
theme: ThemeData(
primaryColor: Colors.red,
accentColor: Colors.orangeAccent,
primarySwatch: Colors.red,
),
home: MainApp(),
));class MainApp extends StatefulWidget {
@override
_MainAppState createState() => _MainAppState();
}class _MainAppState extends State<MainApp> {
String _jsonContent = ""; @override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Flutter JSON", style: TextStyle(color: Colors.white,),),
),
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
onPressed: () {
// TODO: do something in here
},
child: Text("Read JSON File"),
),
],
),
Padding(
padding: const EdgeInsets.only(top: 12.0),
child: Text(_jsonContent, textAlign: TextAlign.center,),
),
],
),
),
),
);
}
}
Dimana, output dari kode diatas adalah sebagai berikut.
Jadi, pada UI tersebut kita ada menyediakan satu widget RaisedButton dan Text yang mana nantinya kita beri listener onPressed pada RaisedButton tersebut untuk membaca data json-nya dan hasil dari bacaan tersebut kita tampilkan ke dalam widget Text.
JSON Sederhana
Untuk contoh sederhananya, silakan buat file json didalam direktori assets seperti berikut.
{
"name": "Yudi Setiawan",
"age": 25
}
Lalu, kita buat class model-nya seperti berikut.
class Sample {
String name;
int age; Sample({this.name, this.age}); @override
String toString() {
return 'Sample{name: $name, age: $age}';
} factory Sample.fromJson(Map<String, dynamic> json) {
return Sample(
name: json["name"],
age: json["age"]
);
}
}
Pada class model tersebut, bisa kita lihat terdapat 2 variable yaitu variable name dan age. Lalu, kita juga ada buat constructor-nya dan method toString()-nya. Selain itu, ada satu lagi method yang mungkin asing kita lihat untuk pertama kalinya yaitu method fromJson(). Sebenarnya method ini bernama Named Constructor di Dart. Info lebih lanjut mengenai Named Constructor bisa dibaca disini. Named Constructor tersebut kita buat untuk mengubah data json kedalam bentuk class model. Lalu, kenapa kita pakai type dynamic untuk value di Map-nya? Karena, kita tidak pernah tahu didalam json tersebut kira-kira type apa saja yang bakalan tersedia. Contohnya seperti json yang diatas dimana, disitu ada 2 type yang tersedia yaitu type string dan number.
Sekarang kita buat kode untuk membaca file json-nya.
Future _loadSampleJson() async {
String jsonString = await rootBundle.loadString("assets/sample.json");
final jsonData = json.decode(jsonString);
Sample sample = Sample.fromJson(jsonData);
setState(() {
_jsonContent = sample.toString();
// sample.name => you can access field from class model
});
}
Pada kode diatas, terdapat 2 keyword yang mungkin belum kita kenal yaitu, async dan await. Lalu, apa fungsinya kedua keyword tersebut. Berikut penjelasannya.
- async berfungsi untuk memberikan eksekusi bahwa kode tersebut dijalankan secara asynchronous.
- await berfungsi untuk memberikan eksekusi didalam asynchronous bahwa eksekusi await harus selesai terlebih dahulu (suspending function) lalu, setelah selesai await melakukan prosesnya dilanjutkan mengeksekusi kode berikutnya didalam asynchronous. Jika kita pernah belajar di Kotlin Coroutine itu ada istilahnya async dan await kira-kira fungsinya hampir sama. Untuk info selengkapnya mengenai async dan await bisa kita pelajari di sini.
Sekarang, kita panggil fungsi tersebut didalam listener onPressed si RaisedButton.
RaisedButton(
onPressed: () {
_loadSampleJson();
},
child: Text("Read JSON File"),
)
Lalu, jangan lupa ya tambahkan assets file sample.json kita didalam file pubspec.yaml seperti berikut.
name: flutter_json_app
description: Sample JSON in Flutter# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# Read more about versioning at semver.org.
version: 1.0.0+1environment:
sdk: ">=2.0.0-dev.68.0 <3.0.0"dependencies:
flutter:
sdk: flutter # The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^0.1.2dev_dependencies:
flutter_test:
sdk: flutter
# For information on the generic Dart part of this file, see the
# following page: https://www.dartlang.org/tools/pub/pubspec# The following section is specific to Flutter.
flutter: # The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true # To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
assets:
- sample.json # An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.io/assets-and-images/#resolution-aware. # For details regarding adding assets from package dependencies, see
# https://flutter.io/assets-and-images/#from-packages # To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.io/custom-fonts/#from-packages
Jika sudah lalu sync packages get-nya. Dan sekarang coba test jalankan app-nya lalu tap button Read JSON File. Dan lihat apa yang terjadi.
JSON Array sederhana didalam JSON Object
Sekarang silakan kita ubah lagi file sample.json seperti berikut.
{
"name": "Yudi Setiawan",
"age": 25,
"hobi": [
"Coding",
"Main Game",
"Menulis"
]
}
Dikarenakan property hobi merupakan JSON Array maka, kita bisa buat class model-nya seperti berikut.
class Sample {
String name;
int age;
List<String> hobi; Sample({this.name, this.age, this.hobi});
@override
String toString() {
return 'Sample{name: $name, age: $age, hobi: $hobi}';
} factory Sample.fromJson(Map<String, dynamic> json) {
return Sample(
name: json["name"],
age: json["age"],
hobi: List<String>.from(json["hobi"])
);
}
}
Untuk mem-parsing JSON Array seperti pada contoh json diatas maka, kita bisa mem-parsing-nya dengan cara List<String>.from(...)
. Yang perlu diperhatikan pada contoh json diatas adalah kita lihat bahwa JSON Array-nya itu isinya String semua jadi kita tidak perlu membuat class model si item-nya. Sekarang coba jalankan lagi programnya.
JSON Object didalam JSON Object
Untuk contoh berikut kita akan buat json-nya seperti berikut.
{
"name": "Yudi Setiawan",
"age": 25,
"hobi": [
"Coding",
"Main Game",
"Menulis"
],
"github": {
"username": "CoderJava",
"repository": 278
}
}
Pada contoh json diatas kita ada menambahkan property github dimana, property tersebut merupakan JSON Object dan didalamnya terdapat property username dan repository. Untuk mem-parsing property github maka, kita perlu membuat class model-nya sendiri.
class Github {
String username;
int repository; Github({this.username, this.repository}); @override
String toString() {
return 'Github{username: $username, repository: $repository}';
} factory Github.fromJson(Map<String, dynamic> json) {
return Github(
username: json["username"],
repository: json["repository"]
);
}
}
Lalu, pada class model utamanya kita ubah menjadi seperti berikut.
class Sample {
String name;
int age;
List<String> hobi;
Github github; Sample({this.name, this.age, this.hobi, this.github}); @override
String toString() {
return 'Sample{name: $name, age: $age, hobi: $hobi, github: $github}';
} factory Sample.fromJson(Map<String, dynamic> json) {
return Sample(
name: json["name"],
age: json["age"],
hobi: List<String>.from(json["hobi"]),
github: Github.fromJson(json["github"]),
);
}
}
Sekarang coba kita jalankan lagi programnya.
JSON dengan data yang kompleks
Sekarang kita buat data json-nya menjadi lebih kompleks lagi seperti berikut.
{
"name": "Yudi Setiawan",
"age": 25,
"hobi": [
"Coding",
"Main Game",
"Menulis"
],
"github": {
"username": "CoderJava",
"repository": 278
},
"articles": [
{
"id": 1,
"title": "Flutter: Introduction",
"subtitle": "Beautiful native apps with Dart language"
},
{
"id": 2,
"title": "Flutter: LinearLayout",
"subtitle": "How to design LinearLayout in Flutter"
},
{
"id": 3,
"title": "Flutter: RelativeLayout",
"subtitle": "How to design RelativeLayout in Flutter"
}
]
}
Dikarenakan property articles merupakan JSON Array yang mana item-nya merupakan JSON Object maka, kita buat terlebih dahulu class model dari item JSON Object tersebut.
class Article {
int id;
String title;
String subtitle; Article({this.id, this.title, this.subtitle}); @override
String toString() {
return 'Article{id: $id, title: $title, subtitle: $subtitle}';
} factory Article.fromJson(Map<String, dynamic> json) {
return Article(
id: json["id"],
title: json["title"],
subtitle: json["subtitle"]
);
}
}
Lalu, class model utamanya kita ubah menjadi seperti berikut.
class Sample {
String name;
int age;
List<String> hobi;
Github github;
List<Article> articles; Sample({this.name, this.age, this.hobi, this.github, this.articles});
@override
String toString() {
return 'Sample{name: $name, age: $age, hobi: $hobi, github: $github, articles: $articles}';
} factory Sample.fromJson(Map<String, dynamic> json) {
return Sample(
name: json["name"],
age: json["age"],
hobi: List<String>.from(json["hobi"]),
github: Github.fromJson(json["github"]),
articles: List<Article>.from(json["articles"].map((article) {
return Article.fromJson(article);
}))
);
}
}
Jadi, awalnya kita memanggil json["articles"]
yang mana kode ini akan menghasilkan dalam bentuk dynamic
. Lalu, kita mapping objek dynamic
tersebut dengan cara json["articles"].map((value) => ...)
. Didalam mapping tersebut kita parsing item value mapping-nya dengan cara Article.fromJson(value)
. Sehingga jika digabung maka kodenya akan menjadi seperti berikut.
List<Article>.from(json["articles"].map((value) => Article.fromJson(value)));
Atau biar kita lebih gampang membacanya mungkin bisa kita pisah menjadi seperti berikut.
factory Sample.fromJson(Map<String, dynamic> json) {
// baca property articles sebagai List (JSON Array)
var listArticles = json["articles"] as List; // mapping listArticles kedalam bentuk MappedIterable<dynamic, Article>
var iterableArticles = listArticles.map((article) {
return Article.fromJson(article);
}); // lalu, kita konversi dari MappedIterable kedalam bentuk List<Article>
var articles = List<Article>.from(iterableArticles); return Sample(
name: json["name"],
age: json["age"],
hobi: List<String>.from(json["hobi"]),
github: Github.fromJson(json["github"]),
articles: articles
);
}
Dari kode diatas mungkin kita bertanya di kode var listArticles = json["articles"] as List
mengapa kita tidak langsung meng-konversi-nya menjadi seperti berikut var listArticles = json["articles"] as List<Article>
. Untuk mendapatkan jawabannya silakan kita mencobanya seperti berikut.
factory Sample.fromJson(Map<String, dynamic> json) {
// baca property articles sebagai List (JSON Array)
var listArticles = json["articles"] as List<Article>; // mapping listArticles kedalam bentuk MappedIterable<dynamic, Article>
/*var iterableArticles = listArticles.map((article) {
return Article.fromJson(article);
});*/ // lalu, kita konversi dari MappedIterable kedalam bentuk List<Article>
var articles = listArticles; return Sample(
name: json["name"],
age: json["age"],
hobi: List<String>.from(json["hobi"]),
github: Github.fromJson(json["github"]),
articles: articles
);
}
Sekarang coba jalankan programnya.
Maka, kita akan mendapatkan pesan error seperti diatas. “Maksud pesan error tersebut apaan sih?” Biar saya jawab ya. Programnya bilang sama kita bahwa hasil casting kita yang ini json["articles"] as List<Articles>
ternyata salah. Si Flutter ternyata dia itu tidak tahu sama sekali generic type dari List yang kita casting tersebut. Karena dia tidak tahu generic type dari List tersebut maka si Flutter menganggap-nya sebagai objek List<dynamic>
sehingga ketika kita mau melakukan casting dari List<dynamic>
ke List<Article>
maka programnya pun error karena mengalami kegagalan casting.
Sekarang coba kita kembalikan kode program yang error tadi menjadi semula ya.
class Sample {
String name;
int age;
List<String> hobi;
Github github;
List<Article> articles; Sample({this.name, this.age, this.hobi, this.github, this.articles});
@override
String toString() {
return 'Sample{name: $name, age: $age, hobi: $hobi, github: $github, articles: $articles}';
} factory Sample.fromJson(Map<String, dynamic> json) {
// baca property articles sebagai List (JSON Array)
var listArticles = json["articles"] as List; // mapping listArticles kedalam bentuk MappedIterable<dynamic, Article>
var iterableArticles = listArticles.map((article) {
return Article.fromJson(article);
}); // lalu, kita konversi dari MappedIterable kedalam bentuk List<Article>
var articles = List<Article>.from(iterableArticles); return Sample(
name: json["name"],
age: json["age"],
hobi: List<String>.from(json["hobi"]),
github: Github.fromJson(json["github"]),
articles: articles,
);
}
}
Sekarang coba kita jalankan lagi programnya.
JSON dengan dynamic key
Sekarang untuk lebih menantang lagi, coba kita buat data json yang mana key property-nya itu bersifat dynamic seperti berikut.
{
"name": "Yudi Setiawan",
"age": 25,
"hobi": [
"Coding",
"Main Game",
"Menulis"
],
"github": {
"username": "CoderJava",
"repository": 278
},
"articles": [
{
"id": 1,
"title": "Flutter: Introduction",
"subtitle": "Beautiful native apps with Dart language"
},
{
"id": 2,
"title": "Flutter: LinearLayout",
"subtitle": "How to design LinearLayout in Flutter"
},
{
"id": 3,
"title": "Flutter: RelativeLayout",
"subtitle": "How to design RelativeLayout in Flutter"
}
],
"contact": {
"1": {
"name": "Personal",
"phone": "085212345678"
},
"2": {
"name": "Work",
"phone": "085312345678"
}
}
}
Pada data json diatas, yang dynamic ialah nilai di property contact dimana, nilai 1 dan 2 itu bersifat dinamis sesuai dengan jumlah data yang tersedia. Mungkin bisa saja nilai item-nya bernilai 3 atau bahkan lebih dari 3. Sekarang coba kita buat class model dari item property Contact.
class Contact {
String name;
String phone; Contact({this.name, this.phone}); @override
String toString() {
return 'Contact{name: $name, phone: $phone}';
} factory Contact.fromJson(Map<String, dynamic> json) {
return Contact(
name: json["name"],
phone: json["phone"]
);
}
}
Lalu, kita buat class model utamanya seperti berikut.
class Sample {
String name;
int age;
List<String> hobi;
Github github;
List<Article> articles;
Map<String, Contact> contact; Sample({this.name, this.age, this.hobi, this.github, this.articles, this.contact}); @override
String toString() {
return 'Sample{name: $name, age: $age, hobi: $hobi, github: $github, articles: $articles, contact: $contact}';
} factory Sample.fromJson(Map<String, dynamic> json) {
// baca property articles sebagai List (JSON Array)
var listArticles = json["articles"] as List; // mapping listArticles kedalam bentuk MappedIterable<dynamic, Article>
var iterableArticles = listArticles.map((article) {
return Article.fromJson(article);
}); // lalu, kita konversi dari MappedIterable kedalam bentuk List<Article>
var articles = List<Article>.from(iterableArticles); return Sample(
name: json["name"],
age: json["age"],
hobi: List<String>.from(json["hobi"]),
github: Github.fromJson(json["github"]),
articles: articles,
contact: Map.from(json["contact"]).map((key, contact) {
return MapEntry(key, Contact.fromJson(contact));
}),
);
}
}
Awalnya kita baca property contact sebagai Map atau JSON Object dengan cara Map.from(json["contact"])
. Sebelum lanjut ke penjelasannya saya mau bertanya terlebih dahulu. “Kira-kira dari kode Map.from(json["contact"]
ini akan menghasilkan objek Map dengan generic type apa?”. Jika kita fokus pada kasus sebelumnya yang mengenai casting List tadi seharusnya kita bisa menemukan jawabannya yaitu Map tadi akan menghasilkan objek Map dengan type <String, dynamic>
. Kalau tidak percaya coba kita ubah kode diatas menjadi seperti berikut.
class Sample {
String name;
int age;
List<String> hobi;
Github github;
List<Article> articles;
Map<String, Contact> contact; Sample({this.name, this.age, this.hobi, this.github, this.articles, this.contact}); @override
String toString() {
return 'Sample{name: $name, age: $age, hobi: $hobi, github: $github, articles: $articles, contact: $contact}';
} factory Sample.fromJson(Map<String, dynamic> json) {
// baca property articles sebagai List (JSON Array)
var listArticles = json["articles"] as List; // mapping listArticles kedalam bentuk MappedIterable<dynamic, Article>
var iterableArticles = listArticles.map((article) {
return Article.fromJson(article);
}); // lalu, kita konversi dari MappedIterable kedalam bentuk List<Article>
var articles = List<Article>.from(iterableArticles); // tambahkan kode berikut untuk melihat generic type objek Map-nya
var mapContact = json["contact"] as Map;
print("mapContact bertipe: ${mapContact.runtimeType}"); return Sample(
name: json["name"],
age: json["age"],
hobi: List<String>.from(json["hobi"]),
github: Github.fromJson(json["github"]),
articles: articles,
contact: Map.from(json["contact"]).map((key, contact) {
return MapEntry(key, Contact.fromJson(contact));
}),
);
}
}
Sekarang coba jalankan programnya maka, outputnya di console nanti akan kelihatan tipe dari map contact-nya.
Sekarang saya lanjutkan lagi penjelasannya mengenai casting dynamic key tadi ya. Tadi kan awalnya kita membuat objek Map dari kode
Map.from(json["contact"])
atau
json["contact"] as Map;
Sekarang karena tadi Map-nya memiliki generic type-nya <String, dynamic>
maka, langkah selanjutnya ialah kita ubah lagi dynamic
tersebut menjadi class model-nya yaitu Contact. Caranya
// baca root json objek contact
var mapContact = json["contact"] as Map;// baca item json object contact
var mapContactItem = mapContact.map((key, contact) {
return MapEntry(key, Contact.fromJson(contact));
});
Sekarang ubah kode awal kita tadi yang agak susah dibaca seperti berikut.
class Sample {
String name;
int age;
List<String> hobi;
Github github;
List<Article> articles;
Map<String, Contact> contact; Sample({this.name, this.age, this.hobi, this.github, this.articles, this.contact}); @override
String toString() {
return 'Sample{name: $name, age: $age, hobi: $hobi, github: $github, articles: $articles, contact: $contact}';
} factory Sample.fromJson(Map<String, dynamic> json) {
// baca property articles sebagai List (JSON Array)
var listArticles = json["articles"] as List; // mapping listArticles kedalam bentuk MappedIterable<dynamic, Article>
var iterableArticles = listArticles.map((article) {
return Article.fromJson(article);
}); // lalu, kita konversi dari MappedIterable kedalam bentuk List<Article>
var articles = List<Article>.from(iterableArticles); return Sample(
name: json["name"],
age: json["age"],
hobi: List<String>.from(json["hobi"]),
github: Github.fromJson(json["github"]),
articles: articles,
contact: Map.from(json["contact"]).map((key, contact) {
return MapEntry(key, Contact.fromJson(contact));
}),
);
}
}
menjadi seperti berikut biar gampang baca kode parsing contact-nya.
class Sample {
String name;
int age;
List<String> hobi;
Github github;
List<Article> articles;
Map<String, Contact> contact; Sample({this.name, this.age, this.hobi, this.github, this.articles, this.contact}); @override
String toString() {
return 'Sample{name: $name, age: $age, hobi: $hobi, github: $github, articles: $articles, contact: $contact}';
} factory Sample.fromJson(Map<String, dynamic> json) {
// baca property articles sebagai List (JSON Array)
var listArticles = json["articles"] as List; // mapping listArticles kedalam bentuk MappedIterable<dynamic, Article>
var iterableArticles = listArticles.map((article) {
return Article.fromJson(article);
}); // lalu, kita konversi dari MappedIterable kedalam bentuk List<Article>
var articles = List<Article>.from(iterableArticles); // baca json object contact
var mapContact = json["contact"] as Map; // lanjut baca isi dari mapContact (json object)
var mapContactContent = mapContact.map((key, value) {
return MapEntry<String, Contact>(key, Contact.fromJson(value));
}); return Sample(
name: json["name"],
age: json["age"],
hobi: List<String>.from(json["hobi"]),
github: Github.fromJson(json["github"]),
articles: articles,
contact: mapContactContent,
);
}
}
Sekarang coba jalankan lagi programnya.