แปลง JSON ให้เป็นคลาส Dart ที่ไม่เปลี่ยนรูปด้วย fromJson, toJson, copyWith และการเปรียบเทียบค่าที่เชื่อถือได้
เปิดบทเรียนนี้ใน Kodokonในระดับนี้ ประเด็นที่แท้จริงไม่ใช่ไวยากรณ์ของคลาสอีกต่อไป แต่เป็นเส้นแบ่งระหว่างโลกภายนอกกับโค้ดของคุณ ตราบใดที่การตอบกลับจาก API ยังถูกส่งต่อไปมาในรูป Map<String, dynamic> การอ่านทุกฟิลด์ก็คือการเสี่ยงดวงตอนรันไทม์ กฎของมืออาชีพคือ แปลง JSON ให้เป็นอ็อบเจกต์ที่มีชนิด ทันทีที่คุณได้รับมัน แล้วส่งต่อเฉพาะอ็อบเจกต์เหล่านั้นไปมา เมื่อรวมสิ่งนี้เข้ากับความไม่เปลี่ยนรูป (ฟิลด์ final, คอนสตรักเตอร์ const) คุณจะกำจัดบั๊กได้ทั้งตระกูล นั่นคือ ไม่มีใครสามารถแก้ไขอ็อบเจกต์ที่ใช้ร่วมกันลับหลังคุณได้อีก และอินสแตนซ์ const สองตัวที่เหมือนกันจะถูกคอมไพเลอร์ทำให้เป็นตัวเดียวกัน
class User {
const User({
required this.id,
required this.name,
this.email,
});
factory User.fromJson(Map<String, dynamic> json) {
return User(
id: (json['id'] as num).toInt(),
name: json['name'] as String,
email: json['email'] as String?,
);
}
final int id;
final String name;
final String? email;
}มีสามทางเลือกที่ควรอธิบายให้ชัด อย่างแรก (json['id'] as num).toInt() API จำนวนมากทำให้จำนวนเต็มเป็นอนุกรม (serialize) บางครั้งในรูป 3 บางครั้งในรูป 3.0 ขึ้นอยู่กับแบ็กเอนด์ การแคสต์ตรง ๆ เป็น int จะพังเมื่อเจอ double ต่อมา as String จะล้มเหลวทันทีพร้อม TypeError ที่ชัดเจนถ้าฟิลด์นั้นหายไป ซึ่งนั่นคือความตั้งใจ การล้มเหลวเร็วตั้งแต่ตอนแยกวิเคราะห์ ดีกว่าปล่อยให้ null แพร่กระจายไปแล้วไประเบิดเอาในอีกสามหน้าจอถัดไป และสุดท้าย as String? เป็นการระบุไว้ในชนิดของข้อมูลว่าฟิลด์นี้เป็นทางเลือกได้จริง ๆ
class User {
const User({required this.id, this.email});
final int id;
final String? email;
Map<String, dynamic> toJson() => {
'id': id,
'email': email,
};
User copyWith({int? id, String? email}) {
return User(
id: id ?? this.id,
email: email ?? this.email,
);
}
}ความไม่เปลี่ยนรูปเรียกร้องการเปรียบเทียบด้วยค่า โดยค่าเริ่มต้น Dart เปรียบเทียบด้วยการอ้างอิง (reference) นั่นคือ User สองตัวที่เหมือนกันทุกฟิลด์จะถูกมองว่า "ต่างกัน" ซึ่งเป็นปัญหาที่เป็นรูปธรรมทันทีที่คุณเปรียบเทียบสถานะ ไม่ว่าจะในเทสต์ ใน select ของ Provider หรือในการค้นหาในรายการ จงนิยาม == และ hashCode ใหม่คู่กันเสมอ
class Point {
const Point(this.x, this.y);
final int x;
final int y;
@override
bool operator ==(Object other) =>
other is Point && other.x == x && other.y == y;
@override
int get hashCode => Object.hash(x, y);
}(json['id'] as num).toInt() แทนที่จะเป็น json['id'] as int?as int ช้ากว่า as num ตอนรันไทม์3.0 การแคสต์ตรง ๆ เป็น int จะล้มเหลวเมื่อเจอ doublejson['id'] เป็นสายอักขระเสมอuser.copyWith(email: null) ให้ผลลัพธ์อะไรเมื่อใช้การพัฒนา copyWith แบบดั้งเดิม?email ถูกรีเซ็ตเป็น null ตามที่ร้องขอemail ปัจจุบันไว้ เพราะ null ?? this.email ย้อนกลับไปใช้ค่าเดิม== และ hashCode ใหม่บนโมเดลที่ไม่เปลี่ยนรูป?constfinal