การใช้งาน JSON String Data ในภาษา Dart เบื้องต้น

บทความใหม่ ยังไม่ถึงปี โดย Ninenik Narkdee
json dart convert json serializable dart

คำสั่ง การ กำหนด รูปแบบ ตัวอย่าง เทคนิค ลูกเล่น การประยุกต์ การใช้งาน เกี่ยวกับ json dart convert json serializable dart





ในการแลกเปลี่ยนรับส่งข้อมูลระหว่าง Server ส่วนใหญ่ทั้งการใช้งาน
Mobile App หรือ Web App จะมีการใช้งานข้อมูลในรูปแบบ JSON ที่
เป็นข้อมูล Text ซึ่งสามารถแปลงไปเป็น Object เพื่อเรียกใช้งาน หรือใน Web
ก็จะเรียกว่า JavaScript Object
    เนื้อหาในตอนต่อไปนี้ เราจะมาดูเกี่ยวกับการใช้งาน JSON ในภาษา Dart
ว่ามีวิธีการเรียกใช้งานอย่างไรบ้าง
 
    ก่อนเข้าสู่เนื้อหา ขอเพิ่มเติมการใช้งาน Visual Studio Code ในการเขียนโปรแกรมภาษา Dart
ซึ่งจากเดิม เราจะเรียกใช้งานผ่าน Dartpad.dev https://dartpad.dev/ เพื่อทดสอบโค้ดต่างๆ และ
นั่นก็ถือว่าพอสมควรกับการใช้งาน และการแนะนำโค้ดเบื้องต้น ตามเนื้อหาที่ผ่านๆ มา  แต่เนื่องจาก
เนื้อหาของเราเริ่มมีความเข้มข้นขึ้น และอาจจะต้องใช้งานบาง library หรือ package เพิ่มเข้ามา รวม
ถึงอาจจะมีการจัดการเกี่ยวกับไฟล์ ดังนั้น เราจะมาพัฒนาโปรแกรมภาษา Dart ต่อโดยใช้งาน VSCode
    ให้ทำการติดตั้งทุกอย่างให้เรียบร้อย ตามเนื้อหาบทความด้านล่าง
    เริ่มต้นใช้งาน Flutter พัฒนา Nativiely Compiled Apps เบื้องต้น http://niik.in/937 
 
    ดูในส่วนของการใช้งาน VSCode โดยเฉพาะการติดตั้งที่เกี่ยวกับการใช้งาน Dart แต่ถ้าเป็นไปได้ ให้ทำตามทุกขั้นตอน
และติดตั้งส่วนอื่นๆ ให้ครบถ้วน เพราะ Flutter ก็เป็นส่วนหนึ่งของเนื้อหาการประยุกต์ใช้งาน ภาษา Dart 
    หากติดตั้งตามตัวอย่างเรียบร้อยแล้ว ให้เราเปิดโปรแกรม VSCode แล้ว ไปที่เมนู View > Command Pallete.. หรือกด
Ctrl + Shift + P แล้ว พิมพ์คว่า Dart แล้วเลือก Dart New Project...
 
 

 
 
    จะขึ้นให้ติดตั้ง Extension เพิ่มเติม ตามรูป กรณียังไม่ได้ทำการติดตั้ง
 
 

 
 
    กด Activate Stagehand รอสักครู่..... จนทำงานเสร็จ ให้เรา เลือกสร้าง Dart New project ใหม่อีกครั้ง  จะมีรูปแบบ Template
สำหรับการพัฒนาด้วยภาษา Dart ในที่นี้เราจะใช้เป็น Console Application
 
 

 
 
    ตั้งชื่อ ในที่นี้ใช้ชือว่า "myapp" กด Enter
 
 

 
 
    เลือกโฟลเดอร์ที่ต้องการ ในที่นี้เก็บไว้ในโฟลเดอร์ dart ที่รวมโปรเจ็คเกี่ยวกับ Dart
 
 

 
 
    รอให้โปรแกรมทำงานสักครู่ ตัวโปรแกรมจะทำการดาวน์โหลด และสร้าง Application โปรเจ็คมาให้ นั่นคือเราจะได้ชื่อโฟลเดอร์
"myapp" ที่เป็นโปรเจ็ค dart ของเรา และมีไฟล์ต่างๆ ด้านใน
 
 

 
 
    เราจะสนใจเฉพาะไฟล์ main.dart ในโฟลเดอร์ bin ลบข้อมูลในไฟล์เดิม แล้วพิมพ์
 
void main(){
  print('Hellow world');
}
    จากนั้นกด F5 เพื่อรัน คำสั่ง Debug โค้ด ดูการทำงาน ผลลัพธ์จะแสดงที่ส่วนของ Debug Console
 
 

 
 
    ตอนนี้เราพร้อมใช้งาน VSCode ศึกษาและพัฒนาการใช้งานภาษา Dart 
 
 

 

การใช้งาน JSON String Data

    การใช้งาน JSON String Data ในภาษา Dart สามารถใช้งาน Library และ Package ต่อไปนี้จัดการ ได้แก่
 
    สำหรับ dart:convert Library จะใช้สำหรับการใช้งานอย่างง่าย และไม่ได้มีการจัดการเกี่ยวกับข้อมูล JSON ที่ซับซ้อนนัก โดยเป็น
Library ที่ใช้สำหรับ แปลงข้อมูล JSON และ ข้อมูล UTF-8 (การเข้ารหัสตัวอักขระ ที่ข้อมูล JSON จำเป็นต้องใช้) 
    ส่วน package:json_serializable จะเป็นตัวที่ช่วยให้สามารถจัดการข้อมูล JSON ได้ดีขึ้น รองรับรูปแบบข้อมูล JSON ที่มีโครงสร้าง
ซับซ้อนเช่นมีการ ซ้อนกันของข้อมูลหลายๆ ขั้น ทำให้เราสามารถแปลงข้อมูล JSON มาเป็น Object ที่ถูกต้องและเรียกใช้งานได้ง่าย
รายละเอียดจะได้เพิ่มเติมในแต่ละหัวข้อ และสุดท้าย package:built_value เป็น package ที่เป็นอีกหนึ่งตัวเลือก ที่สามารถใช้แทน 
json_serializable package ได้ เราจะได้ลงรายละเอียดในแต่ละตัว ตามความเหมาะสม
 
 

    การใช้งาน dart:convert

    dart:convert library ใช้แปลงข้อมูล JSON และ UTF-8  โดยในการใช้งานกับข้อมูล JSON จะเป็นการแปลงข้อมูลที่เป็นข้อความ
ที่มีรูปแบบโครงสร้างของ Object และอาเรย์หรือชุดข้อมูล ส่วนการใช้งาน UTF-8 จะเป็นการเข้ารหัส ถอดรหัสตัวอักขระในรูปแบบข้อมูล
Unicode  
    เราสามารถใช้งาน dart:convert ได้ทั้งการพัฒนา web หรือแบบ command line โดยหากมีการใช้งาน ต้องทำการ import ดังนี้
 
import 'dart:convert';
    การ Decoding และ encoding JSON
    การแปลงข้อมูล JSON String Data เป็น Dart Object ด้วยการ Decoding สามารถใช้คำสั่ง jsonDecode() ดูตัวอย่าง
การใช้งานการแปลงข้อมูล JSON มาเป็น List หรือ Dart Object 
 
import 'dart:convert';

void main(){

  // จำลองข้อมูล JSON String Data ไว้ใจตัวแบ่งการกำหนดข้อความ
  // ในโปรแกรมภาษา Dart ในที่นี้ใช้ตัวคั่น เป็น (''')
  var jsonString = '''
    [
      {"score": 40},
      {"score": 80}
    ]
  ''';

  // ทำการถอดรหัส โดยใช้คำสั่ง jsonDecode()
  var scores = jsonDecode(jsonString);
  // ทดสอบดูชนิดข้อมูล หรือ Dart Object ที่ได้จากการเแปลงข้อมูล
  print(scores.runtimeType); // List<dynamic>
  print(scores); // [{score: 40}, {score: 80}]
  print('First score is ${scores[0]['score']}');
  // First score is 40

}
    จะเห็นว่าข้อมูลที่ได้จากการแปลง JSON String Data จะได้เป็น List<dynamic> ตามรูปแบบโครงสร้างของข้อมูล ตามตัวอย่างจะได้
เป็น List<Map<String, dynamic>>
 
    การแปลงข้อมูลจาก Dart Object ไปเป็น JSON String Data จะใช้คำสั่ง jsonEncode() ดูตัวอย่างด้านล่างประกอบ
 
import 'dart:convert';

void main(){

  // ตัวแปร sources เป็น List ที่มีข้อมูลที่เป็นแบบ Map และ Set ด้านใน
  var scores = [
    {'score': 40},
    {'score': 80},
    {'score': 100, 'overtime': true, 'special_guest': null}
  ];

  // ทำการแปลง Dart Objext เป็น JSON String Data
  var jsonText = jsonEncode(scores);
  print(jsonText.runtimeType); // String
  print(jsonText);
  // [{"score":40},{"score":80},{"score":100,"overtime":true,"special_guest":null}]
  
}
    จะเห็นว่า เราใช้คำสั่้ง jsonEncode() แปลงข้อมูล Dart Object มาเป็น JSON String Data ซึ่งเป็นข้อมูล String ตามตัวอย่าง
สังเกตว่า JSON จะมี key เป็น String และใช้ double qoute (") กำกับ
    โดย Object ที่รองรับการแปลงเป็น JSON ได้แก่ int, double, String, bool, null, List หรือ Map (ที่มี key เป็น String)
การแปลงข้อมูลประเภท List และ Map จะเป็นลักษณะการวนทำซ้ำจนครบรายการ
 
    กรณีที่เป็น Object ที่ไม่สามารถแปลงเป็น JSON โดยตรง สามารถแก้ไขได้ใน 2 แบบ คือแบบแรก เพิ่มฟังก์ชั่นสำหรับจัดการการ
ข้อมูลให้เป็นชนิดที่สามารถแปลงเป็น JSON ได้ เข้าไปเป็น argument ตัวที่ 2 ของคำสั่ง jsonEncode(arg1, arg2) หรือแบบที่ 2 
สร้างฟังก์ชั่นใน model class ข้อมูล หรือในแบบจำลองข้อมูล ชื่อวา toJson() เมื่อเราเรียกใช้คำสั่ง jsonEncode()  ฟังก์ชั่น toJson()
จะถูกเรียกใช้งานอัตโนมัติ
 
    มาดูตัวอย่างข้อมูล Object ที่ไม่สามารถแปลงเป็น JSON ได้โดยตรง เช่น Address Object ตาม medel class ดังนี้
 
void main(){

  // สร้าง Address Object 
  var address = Address('My st.','New York');

  // ทดสอบแปลง Address Object เป็น JSON จะเกิด Error ขึ้น
  var jsonText = jsonEncode(address);
// JsonUnsupportedObjectError (Converting object to an encodable object failed: 
// Instance of 'Address')  
}
class Address {
  String street;
  String city;

  Address(this.street, this.city);
}
    วิธีแก้ปัญหาแบบแรก
 
void main(){

  var address = Address('My st.','New York');
  // กำหนดรูปแบบข้อมูลที่สามารถแปลงเป็น JSON ได้
  var encodableData = {
    'street': address.street,
    'city': address.city
  };

  // เพิ่ม argument ตัวที่ 2 return ค่าข้อมูลที่สามรถแปลงเป็น JSON ได้
  var jsonText = jsonEncode(address, toEncodable:(address){
    return encodableData;
  });
  print(jsonText.runtimeType); // String
  print(jsonText);

}
class Address {
  String street;
  String city;

  Address(this.street, this.city);
}
 
    วิธีแก้ปัญหาแบบที่สอง
 
void main(){

  var address = Address('My st.','New York');

  var jsonText = jsonEncode(address);
  print(jsonText.runtimeType); // String
  print(jsonText);

}
class Address {
  String street;
  String city;
  Address(this.street, this.city);

  // เพิ่มคำสั่ง toJson() คืนค่าเป็นข้อมูล Map<String, dynamic>
  // ที่สามารถแปลงเป็น JSON ได้ คำสั่งนี้จะถูกเรียกใช้งานอัตโนมัติ
  Map<String, dynamic> toJson() =>
    {
      'street': street,
      'city': city,
    };  
}
    จะเห็นว่าวิธีที่ 2 เป็นรูปแบบที่ง่ายกว่า และเวลาเรียกใช้งาน ก็ใช้รูปแบบเดิม ไม่ต้องเพิ่มเติม argument 
 
    เราอาจจะเพิ่มเติม โดยการกำหนด named constructor ที่ทำให้สามารถสร้าง Object โดยใช้ข้อมูลที่สามารถแปลงเป็น JSON 
หรือข้อมูล ในรูปแบบ Map<String, dynamic> ตัวอย่างเช่น
 
void main(){

  // var address = Address('My st.','New York');
  
  // สร้าง object โดยใช้ named constuctor 
  var address = Address.fromJson({
      'street': 'My st.',
      'city': 'New York'
    });
  var jsonText = jsonEncode(address);
  print(jsonText.runtimeType); // String
  print(jsonText);

}
class Address {
  String street;
  String city;
  Address(this.street, this.city);


  Address.fromJson(Map<String, dynamic> json)
      : street = json['street'],
        city = json['city'];

  // named contructor แบบเต็ม จากด้านบน
/*   Address.fromJson(Map<String, dynamic> json) {
    street = json['street'];
    city = json['city'];
  } */

  Map<String, dynamic> toJson() =>
    {
      'street': street,
      'city': city,
    };  
}
    ถึงแม้ว่า จะจัดการกับ Object ที่ไม่สามารถแปลงเป็น JSON โดยตรงได้ ด้วย 2 วิธีข้างต้น ซึ่งเหมาะกับการใช้งานข้อมูลที่ไม่ซับซ้อน
และ การใช้งาน dart:convert ก็ถือว่าเพียงพอ สำหรับกรณีข้างต้น  แต่ถ้าโปรแกรมของเรา มีความซับซ้อนมากขึ้น มีโครงสร้างข้อมูล
ที่ซับซ้อน เช่นข้อมูลในรูปแบบที่ซ้อนกันหลายๆ ชั้น ยกตัวอย่าง
 
  {
    'name': 'Ebiwayo',
    'address':{
      'street':'My St.',
      'city':'New York'
    }
  }
 
    และส่วนใหญ่ข้อมูลที่ใช้งานผ่าน Server ที่เป็น REST API หรือ Web Service ต่างๆ ก็จะมีลักษณะคล้ายๆ กับตัวอย่าง มีโครงสร้างข้อมูล
ที่ซ้อนกันหลายๆ ชั้น  ดังนั้น จึงจำเป็นที่จะต้องใช้งาน Package ตัวอื่นเพิ่มเติม เพื่อให้การทำงานสะดวก และมีประสิทธิภาพในการจัดการ
ข้อมูล JSON มากขึ้น หัวข้อถัดไป เราจะไปดูต่อที่ การใช้งาน package:json_serializable
 
    ก่อนไปหัวข้อถัดไป ข้อเพิ่มเติมการใช้งาน dart:convert ในส่วนของการแปลงข้อมูล UTF-8 เล็กน้อยดังนี้
    เราสามารถแปลงข้อมูล String เป็นข้อมูล Unicode เข้ารหัส  UTF-8 ด้วยคำสั่ง utf8.encode(mystr) โดยจะ return ค่ากลับมาเป็น
ข้อมูลประเภท List<int> หรือ อาเรย์ของกลุ่มตัวเลข interger หรือฐาน 10 ของแต่ละอักขระ เช่น
 
  String mystr = 'ABC';

  List<int> encoded = utf8.encode(mystr);
  print(encoded); // แสดงข้อมูล [65, 66, 67]
  encoded.forEach(print); // วนลูปแสดงแต่ละค่า
    ข้อความ 'ABC' ประกอบด้วยตัวอักษร A B และ C มีทั้งหมด 3 ตัวอักษร ตัวอักขระละ 1 bytes (8 bit) 
 
Hexadecimal: 0x41 0x42 0x43
Decimal: 65 66 67
 
    มาดูกรณีเป็นภาษาไทย ตัว "ภ" ตัวเดียว จะเป็นเท่าไหร่
 
Hexadecimal: 0xe0 0xb8 0xa0
Decimal: 224 184 160
 
    สังเกตว่าตัวอักษรภาษาไทย 1 ตัว มีค่าเท่ากับ 3 byte แต่ตัวอักษรภาษาอังกฤษ 1 ตัวเท่ากับ 1 byte
    การใช้งาน UTF-8 encoder/decoder ก็คือการแปลงข้อมูลระหว่างข้อความ String กับ Byte
 
    เราสามารถแปลงข้อมูลที่เข้ารหัส UTF-8 ที่อยู่ในรูปแบบ List<int> หรืออาเรย์ของข้อมูลตัวเลข ที่อาจจะเป็น Hexademical หรือ 
Decimal มาเป็นข้อความ String โดยใช้คำสั่ง utf8.decode(utf8Bytes) ดูตัวอย่างเราแปลงข้อมูล 'ABC' ที่เป็น
ฐาน 16 กลับมาเป็นข้อความดังนี้
 
void main(){

  var utf8HexBytes = [
    0x41, 0x42, 0x43
  ];
  var myStr = utf8.decode(utf8HexBytes);
  print(myStr); // ABC

}
    เราสามารถแปลงข้อมูล Stream ที่เข้ารหัส UTF-8 เป็นข้อความ String โดยใช้งานดังนี้
    หากไม่เข้าใจเกี่ยวกับข้อมูล Stream ดูเพิ่มเติมได้ที่บทความ
    ข้อมูล Stream การสร้าง และใช้งาน Stream ในภาษา Dart เบื้องต้น http://niik.in/962
 
import 'dart:convert';

void main() async {

  var stream = genStream(); // จำลองสร้างข้อมูล Stream
  // ทำการถอดรหัสข้อมูล UTF8 และแยกข้อมูลเป็นบรรทัดๆ (ถ้ามีหลายบรรทัด)
  // ตัวแปร lines คือ Stream ข้อมูลใหม่ที่ได้จากการแปลงข้อมูลด้วย Transformer
  var lines = utf8.decoder
              .bind(stream)
              .transform(LineSplitter());

  try {
    // เรียกใช้งานข้อมูล Stream
    await for (var line in lines) {
      print(line); // ABC
      print('Got ${line.length} characters from stream');
      // Got 3 characters from stream
    }
  } catch (e) {
    print(e);
  }

}

// ฟังก์ชั่น จำลองสร้างข้อมูล Stream เป็น List<int> เลขฐาน
Stream<List<int>> genStream() async* {
  // ข้อมูลข้อความ "ABC" เข้ารหัส UTF8 ใน List<int>
  var utf8Bits = [
    0x41, 0x42, 0x43
  ];  
  // หน่วงเวลา 1 วินาที
  await Future.delayed(const Duration(seconds: 1));
  yield utf8Bits;  // ส่งออกข้อมูล Stream
}
    จากตัวอย่างข้างต้น เราสร้างฟังก์ชั่นชื่อ genStream() ที่ทำการส่งออกข้อมูล Stream เป็น List<int> รายการเข้ารหัสข้อมูล UTF8
ในตัวอย่างคือค่าเข้ารหัส UTF8 ในเลขฐาน 16 ของตัว "ABC"
    เราทดลองใช้งานโดยสร้างตัวแปรชื่อ stream รับค่าข้อมูล Stream จากนั้นใช้ตัว utf8.decoder ซึ่งเป็นตัว Transformer หรือตัว
แปลงข้อมูล จับ Stream ที่ต้องการแปลงด้วยคำสั่ง bind()  เริ่มต้น ตัว utf8.decoder จะทำการแปลงข้อมูลใน Stream จาก  UTF8 ก่อน
แล้ว ต่อมาก็ใช้ฟังก์ชั่น transform() แปลงเป็นข้อมูล Stream ใหม่ โดยแยกๆ เป็นบรรทัดๆ ตามการขึ้นบรรทัดใหม่ของข้อมูล (ถ้ามี)
ด้วยคำสั่ง LineSplitter()
    ในขั้นตอนการแปลงข้อมูล Stream เราสามารถใช้ฟังก์ชั่น transform() ทั้งสองครั้งก็ได้ โดยกำหนด Transfromer ที่ต้องการใช้งาน
 
  var lines = stream.transform(Utf8Decoder())
                    .transform(LineSplitter());
    Transfromer ที่เราจะคุ้นตาบ้างก็เช่น JsonDecoder(), JsonEncoder(), LineSplitter(), Utf8Decoder(), Utf8Encoder(), 
AsciiDecoder(), AsciiEncoder(), Base64Decoder(), Base64Encoder(), HtmlEscape() เป็นต้น
 
    สามารถดู property และ method และการใช้งานเพิ่มเดิมได้ที่ dart:convert library
    
 
 

    การใช้งาน package:json_serializable

    จากที่เกริ่นในหัวข้อการใช้งาน dart:convert เพื่อจัดการข้อมูล JSON ที่ว่าเมื่อโปรแกรมของเรามีความซับซ้อน โครงสร้างข้อมูล JSON
มีโครงสร้างการซ้อนกันหลายๆ ชั้น การใช้งาน dart:convert อาจจะไม่เพียงพอ และไม่สะดวกในการจัดการ ดังนั้น จึงมีอีกวิธีการ นั่น
ก็คือการใช้งาน json_serializable package 
    json_serializable เป็น package ที่ช่วยสร้าง generate code หรือสร้างโค้ดสำหรับกำหนดรูปแบบข้อมูลในการใช้งาน และจัดการ
ข้อมูล JSON โดยในการ generate code จะใช้วิธีการ กำหนดคำอธิบาย หรือคำจำกัดความ ที่ตัว package มีมาให้ เพื่อสร้างโค้ดจากคำ
หรือหมายเหตุนั้นๆ เช่น ใช้ข้อความ "@JsonSerializable" สำหรับสร้างโค้ด ถอดรหัสหรือเข้ารหัส JSON  สามารถใช้ข้อความ "@JsonKey"
เพื่อกำหนดฟิลด์ข้อมูลที่ต้องการ เป็นต้น
    ก่อนใช้งาน ให้เราทำการติดตั้ง package ก่อน โดยแก้ไขไฟล์ pubspec.yaml ดังนี้
 
 

 
 
    เราทำการเพิ่ม json_serializable: ^3.2.5 เข้าไปในส่วนของ dependencies เสร็จแล้วกด Save ตัว VSCode จะทำการดาวน์โหลด
และติดตั้งให้อัตโนมัติ หรือถ้าไม่มีการทำงานอัตโนมัติ หรือไม่มีอะไรเกิดขึ้น ให้ใช้คำสั่ง flutter pub get ในส่วนของ Terminal ของโปรเจ็ค
โดยนอกจากจะดาวนโหลด json_serializable package ที่ใช้ generate code แล้ว ก็ยังดาวน์โหลด json_annotation package ที่ใช้
กำหนดคำอธิบาย หรือคำจำกัดความสำหรับใช้ในการ generate code ด้วย
    ในหัวข้อที่ผ่านมาเราสร้าง Address model class หรือต้นแบบข้อมูลของ Address ทบทวนโค้ดดังนี้
 
class Address {
  String street;
  String city;
  Address(this.street, this.city);

  Address.fromJson(Map<String, dynamic> json)
      : street = json['street'],
        city = json['city'];

  Map<String, dynamic> toJson() =>
    {
      'street': street,
      'city': city,
    };  
}
    ทีนี้เราจะใช้วิธีการ generate code แทน เริ่มตนให้เราสร้างไฟล์ address.dart และกำหนดโค้ดดังนี้
 
 

 
 
import 'package:json_annotation/json_annotation.dart';

part 'address.g.dart';

@JsonSerializable()

class Address {  
  String street;
  String city;
  Address(this.street, this.city);

  factory Address.fromJson(Map<String, dynamic> json) 
      => _$AddressFromJson(json);

  Map<String, dynamic> toJson() => _$AddressToJson(this);
}
    จะเห็นโค้ดข้างต้น จะมี error ขึ้นเตือน เพราะยังไม่สมบูรณ์ เราจะใช้ไฟล์นี้ในการ generate code แต่ก่อนอื่น ให้เราติดตั้ง
build_runner packgage ก่อน ทำเหมือนเดิมในไฟล์ pubspec.yaml 
 
 

 
 
    จริงๆ แล้วเราสามารถกำหนดทั้ง  json_serializable และ build_runner ไว้ในส่วนของ dev_dependencies เพราะ package นี้เราใช้สำหรับ generate code
เท่านั้น ไม่ได้มีใช้งานโปรแกรมหรือ App ของเราโดยตรง หรือใช้เฉพาะในขั้นตอนการพัฒนา อย่างไรก็ตาม ให้คงแบบตามรูปก่อนก็ได้
    เมื่อติดตั้ง build_runner เราก็พร้อมทำการ สร้างไฟล์ โดยใช้คำสั่ง flutter pub run build_runner build ในส่วนของ Terminal ของ
โปรเจ็ค App ของเรา จะได้เป็นดังนี้
 
 

 
 
    รอสักครู่ เราก็ได้จะได้ไฟล์ address.g.dart และส่วนของไฟล์ address.dart ก็ไม่เกิด error แล้ว
 
 

 
 
    เราลองเปิดไฟล์ address.g.dart จะได้เป็นดังนี้
 
 

 
 
// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'address.dart';

// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************

Address _$AddressFromJson(Map<String, dynamic> json) {
  return Address(
    json['street'] as String,
    json['city'] as String,
  );
}

Map<String, dynamic> _$AddressToJson(Address instance) => <String, dynamic>{
      'street': instance.street,
      'city': instance.city,
    };
    จะเห็นว่า แทนที่เราจะต้องกำหนดฟังก์ชั่นสำหรับจัดการรูปแบบข้อมูล JSON ของ Address model class เองทั้งหมด เราก็แค่ ทำการ
กำหนดการใช้งาน คำอธิบาย @JsonSerializable() ในไฟล์ class ต้นฉบับตามรูปแบบที่กำหนด แล้วทำการ generate ซึ่งถ้าเป็นโครงสร้าง
ข้อมูลง่ายๆ อย่างข้างต้น ก็อาจจะดูไม่ค่อยเห็นผลอะไร แต่ถ้าเป็น class  ข้อมูลที่มีความซับซ้อน มีหลายชั้น การใช้งาน json_serializable 
ก็จะทำให้การจัดการข้อมูลสำหรับใช้งาน JSON มีประสิทธิภาพ สะดวก และรวดเร็วขึ้น 
    เรามาลองทดสอบเรียกใช้งาน Address class ในไฟล์ main() ดูการทำงาน ดังนี้
 
import 'dart:convert';
import 'address.dart';

void main()  {

  var address = Address('My str.','New York');
  var jsonData = jsonEncode(address);
  print(jsonData.runtimeType); // String
  print(jsonData); // {"street":"My str.","city":"New York"}

  Map addressDecode = jsonDecode(jsonData);
  print(addressDecode.runtimeType); // Map<String, dynamic>
  print(addressDecode); // {street: My str., city: New York}
  
  var address2 = Address.fromJson(addressDecode);
  print(address2.runtimeType); // Address
  print(address2.street); // My str.

}
    เราสามารถใช้งานคำสั่ง jsonDecode() และ jsonEncode() ร่วมกับข้อมูลโดยไม่ต้องแก้ไขอะไร จากหัวข้อการใช้งาน dart:convert
นอกจากนั้น เรายังสามารถสร้าง Object จากข้อมูล Map โดยใช้ factory named constructor ชื่อ Address.fromJson()
 
    เรามาลองอีกตัวอย่าง ให้มีความซับซ้อนมากขึ้น เช่น เราสร้าง User model class ที่มีการใช้งาน Address ภายใน ให้กำหนดไฟล์
user.dart ดังนี้
 
 

 
 
import 'address.dart';
import 'package:json_annotation/json_annotation.dart';
part 'user.g.dart';

@JsonSerializable()
class User {
  String firstName;
  Address address;

  User(this.firstName, this.address);

  factory User.fromJson(Map<String, dynamic> json) 
      => _$UserFromJson(json);
  Map<String, dynamic> toJson() => _$UserToJson(this);
}
    ทำเหมือนเดิม รันคำสั่ง flutter pub run build_runner build เพื่อสร้างไฟล์ user.g.dart
    จะได้ไฟล์ user.g.dart เริ่มมีความซับซ้อนมากขึ้น ตามโครงสร้างของข้อมูล
 
// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'user.dart';

// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************

User _$UserFromJson(Map<String, dynamic> json) {
  return User(
    json['firstName'] as String,
    json['address'] == null
        ? null
        : Address.fromJson(json['address'] as Map<String, dynamic>),
  );
}

Map<String, dynamic> _$UserToJson(User instance) => <String, dynamic>{
      'firstName': instance.firstName,
      'address': instance.address,
    };
    ทดสอบใช้งานในไฟล์ main.dart
 
import 'dart:convert';
import 'address.dart';
import 'user.dart';

void main()  {

  var address = Address('My str.','New York');
  var user = User('Ebiwayo', address);
  var jsonData = jsonEncode(user);
  print(jsonData.runtimeType); // String
  print(jsonData); 
  // {"firstName":"Ebiwayo","address":{"street":"My str.","city":"New York"}}

}
    คำว่า "part of" เป็นการระบุว่าไฟล์นั้นๆ เป้นส่วนหนึ่งของ package หรือ library หรือ ไฟล์ใดๆ เพื่อให้สามารถเขียนโค้ดแยกเป็นอีก
ไฟล์ได้ ซึ่งปกติ package หรือ library มักจะเป็นไฟล์ๆ หนึ่ง การแยกอีกไฟล์ก็เหมือนอีก package แต่ การแยกอีกไฟล์โดยการกำหนด
การใช้งาน "part of" จะเป็นการแยกไฟล์ที่ไม่ใช้อีก package แต่เป็นส่วนหนึ่งของ package ตามชื่อหรือ path ที่ระบุนั่นเอง
 
    เราลงลึกรายละเอียดเกี่ยวกับการใช้งานข้อมูล JSON String Data ในภาษา Dart ไปมากพอสมควร ถึงแม้จะยังเหลืออีกหนึ่งหัวข้อ
แต่นั้นก็เป็นเพียงตัวเลือก เราจะข้ามไปก่อน หากสนใจ สามารถศึกษา และใช้งานได้ที่ package:built_value
    ซึ่งถ้ามีความน่าสนใจยังไง อาจจะมาเพิ่มเติมเนื้อหาให้ภายหลัง
 
    หวังว่าจะเป็นประโยชน์ในการทำความเข้าใจ และประยุกต์ใช้งาน JSON ในภาษา Dart รวมถึงการนำไปประยุกต์ใช้กับ Flutter ต่อไป


   เพิ่มเติมเนื้อหา ครั้งที่ 1 วันที่ 29-12-2019


 

เว็บไซต์ที่ให้บริการแปลงโครงสร้างข้อมูล JSON เป็น Dart Model class

    ในบางครั้ง เราอาจจะต้องการความรวดเร็วในการจัดการกับข้อมูล JSON สมมติข้อมูลจาก
https://jsonplaceholder.typicode.com/ ที่เป็นข้อมูลทดสอบ โดยในส่วนของ users จะมีโครงสร้างข้อมูลเป็น
 
{
    "id": 1,
    "name": "Leanne Graham",
    "username": "Bret",
    "email": "Sincere@april.biz",
    "address": {
        "street": "Kulas Light",
        "suite": "Apt. 556",
        "city": "Gwenborough",
        "zipcode": "92998-3874",
        "geo": {
            "lat": "-37.3159",
            "lng": "81.1496"
        }
    },
    "phone": "1-770-736-8031 x56442",
    "website": "hildegard.org",
    "company": {
        "name": "Romaguera-Crona",
        "catchPhrase": "Multi-layered client-server neural-net",
        "bs": "harness real-time e-markets"
    }
}
 
    ซึ่งถ้าหากเราจะต้องมานั่งวางโครงสร้างของ Model class แต่ละข้อมูลก็อาจจะไม่ทันใจ ดังนั้นเราแค่ทำการ copy โครงสร้างข้อมูล
JSON ที่ต้องการ แล้วนำไปวางในเว็บไซต์ https://javiercbk.github.io/json_to_dart/ ตามรูป 
 
 

 
 
 จากนั้นกำหนดชื่อ model class ที่ต้องการ ในตัวอย่างใช้เป็น User เสร็จแล้วกดปุ่ม Generate Dart เราก็ได้ข้อมูลสำหรับนำมาใช้เป็น
Model class เช่น บันทึกไว้ในชื่อไฟล์ user_model.dart  แล้วนำไปใช้งาน  ซึ่งวิธีนี้ทำให้เราไม่ต้องกำหนด model แล้วทำการ generate
ข้อมูลเอง ก็จะสะดวกขึ้น
 
 

การแปลงข้อมูล JSON Array เป็น Dart object

    เนื้อหาที่แนะนำไปข้างต้น เป็นการใช้งาน model class ในลักษณะการแปลงข้อมูล JSON ในรูปแบบ Map<String, dynamic> เป็น
Dart Object ตัวอย่างเช่น ข้อมูลต่อไปนี้
 
  {
    'name': 'Ebiwayo',
    'address':{
      'street':'My St.',
      'city':'New York'
    }
  }
 
    ซึ่งถ้าเราทำการแปลงข้อมูล JSON นี้โดยวิธีที่แนะนำไป ไม่ว่าจะเป็นการใช้งาน dart:convert หรือ json_serializable เราก็จะใช้รูปแบบ
หรือคำสั่งเป็น User.fromJson() ก็จะได้ user object ไปใช้งาน แต่ถ้าข้อมูล JSON มาในรูปแบบของ Array เป็นดังนี้
 
[
   {
      "name": "Ebiwayo",
      "address": {
         "street": "My St.",
         "city": "New York"
      }
   },
   {
      "name": "Zabiny",
      "address": {
         "street": "My Str.2",
         "city": "Miami"
      }
   }
]
 
    ข้อมูล User จะกลายเป็น List<dynamic> แทนที่จะเป็น Map<String, dynamic> ทั้งนี้ก็เพราะว่าเป็นข้อมูลหลายรายการ ดังนั้นเวลา
ใช้งาน เราจะใช้คำสั่ง User.fromJson() กับข้อมูลนี้โดยตรงไม่ได้ เราลองเอา JSON นี้ไปสร้างเป็น User model class ในเว็บไซต์ที่แนะนำ
ด้านบน จะได้ user.dart หรือ user_model.dart แล้วแต่จะตั้งชื่อ เป็นข้อมูล model ของ user ที่เราจะใช้งาน เป็นดังนี้
 
class User {
  String name;
  Address address;

  User({this.name, this.address});

  User.fromJson(Map<String, dynamic> json) {
    name = json['name'];
    address =
        json['address'] != null ? new Address.fromJson(json['address']) : null;
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['name'] = this.name;
    if (this.address != null) {
      data['address'] = this.address.toJson();
    }
    return data;
  }
}

class Address {
  String street;
  String city;

  Address({this.street, this.city});

  Address.fromJson(Map<String, dynamic> json) {
    street = json['street'];
    city = json['city'];
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['street'] = this.street;
    data['city'] = this.city;
    return data;
  }
}
    รูปแบบ class นี้ใช้ได้เฉพาะข้อมูลที่เป็นรายการเดียว แต่ถ้าเป็นแบบ List รายการตามโครงสร้าง JSON ที่กล่าวไป เราจะต้องเพิ่มรูปแบบ
คำสั่ง เพื่อแปลง List<dynamic> ก่อนถึงจะสามารถใช้งานกับ JSON Array ได้ดังนี้
 
class ListUser{
  List<User> users;
  ListUser({this.users});

  factory ListUser.fromJson(List<dynamic> json) {
    return ListUser(
        users: json
            .map((e) => User.fromJson(e as Map<String, dynamic>))
            .toList());
  }

}
    เพิ่ม class นี้เข้าไปในไฟล์ user.dart เท่านี้เราก็สามารถใช้งาน JSON Array แปลงเป็น Dart object ได้แล้ว


กด Like หรือ Share เป็นกำลังใจ ให้มีบทความใหม่ๆ เรื่อยๆ น่ะครับ



อ่านต่อที่บทความ









เนื้อหาที่เกี่ยวข้อง









URL สำหรับอ้างอิง











เว็บไซต์ของเราให้บริการเนื้อหาบทความสำหรับนักพัฒนา โดยพึ่งพารายได้เล็กน้อยจากการแสดงโฆษณา โปรดสนับสนุนเว็บไซต์ของเราด้วยการปิดการใช้งานตัวปิดกั้นโฆษณา (Disable Ads Blocker) ขอบคุณครับ