Kodokon kodokon.com

अपने डेटा को मॉडल करना: क्लास, JSON और अपरिवर्तनीयता

fromJson, toJson, copyWith और भरोसेमंद वैल्यू समानता की मदद से JSON को अपरिवर्तनीय Dart क्लास में बदलें।

9 मिनट · 3 प्रश्न

इस पाठ को Kodokon में खोलें

इस स्तर पर, असली विषय अब क्लास का सिंटैक्स नहीं, बल्कि बाहरी दुनिया और आपके कोड के बीच की सीमा है। जब तक कोई API प्रतिक्रिया Map<String, dynamic> के रूप में इधर-उधर घूमती रहती है, तब तक हर फ़ील्ड का पठन एक रनटाइम जुआ है। पेशेवर नियम: JSON को टाइप किए गए ऑब्जेक्ट में उसी क्षण बदल दें जब आप उसे प्राप्त करते हैं, फिर केवल उन्हीं ऑब्जेक्ट को इधर-उधर पास करें। इसे अपरिवर्तनीयता के साथ जोड़ें - final फ़ील्ड, const कंस्ट्रक्टर - और आप बग के एक पूरे परिवार को समाप्त कर देते हैं: अब कोई भी आपकी जानकारी के बिना किसी साझा ऑब्जेक्ट को नहीं बदल सकता, और दो एक जैसे const इंस्टेंस को कंपाइलर द्वारा कैननिकलाइज़ कर दिया जाता है।

DART
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 पार्सिंग वाला एक अपरिवर्तनीय मॉडल।

तीन विकल्पों को स्पष्ट रूप से समझाना ज़रूरी है। पहला, (json['id'] as num).toInt(): कई API किसी पूर्णांक को बैकएंड के आधार पर कभी 3 के रूप में तो कभी 3.0 के रूप में सीरियलाइज़ करते हैं; सीधे int में कास्ट करने पर double पर यह क्रैश हो जाता है। इसके बाद, यदि फ़ील्ड गायब हो तो as String तुरंत एक स्पष्ट TypeError के साथ विफल हो जाता है: यह जानबूझकर किया गया है। पार्स के समय जल्दी विफल हो जाना उस null से बेहतर है जो फैलता है और तीन स्क्रीन बाद फट पड़ता है। अंत में, as String? टाइप में यह दर्ज कर देता है कि फ़ील्ड वास्तव में वैकल्पिक है।

DART
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,
    );
  }
}
दो फ़ील्ड तक सीमित किए गए मॉडल पर toJson और copyWith।

अपरिवर्तनीयता के लिए वैल्यू समानता ज़रूरी होती है। डिफ़ॉल्ट रूप से, Dart रेफ़रेंस की तुलना करता है: फ़ील्ड-दर-फ़ील्ड एक जैसे दो User को "अलग" माना जाता है। यह एक ठोस समस्या है जैसे ही आप स्टेट की तुलना करते हैं - टेस्ट में, Provider के select में, किसी सूची में खोज में। == और hashCode को हमेशा एक साथ फिर से परिभाषित करें।

DART
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);
}
वैल्यू समानता: == और hashCode एक जोड़ी के रूप में आते हैं।

ज्ञान जांच

सुनिश्चित करें कि आपको इस पाठ के मुख्य बिंदु याद हैं।

  1. json['id'] as int के बजाय (json['id'] as num).toInt() क्यों लिखें?
    • क्योंकि रनटाइम पर as int, as num से धीमा है।
    • क्योंकि कोई बैकएंड किसी पूर्णांक को 3.0 के रूप में सीरियलाइज़ कर सकता है: सीधे int में कास्ट करना double पर विफल हो जाएगा।
    • क्योंकि json['id'] हमेशा एक स्ट्रिंग होती है।
  2. क्लासिक copyWith कार्यान्वयन के साथ user.copyWith(email: null) क्या उत्पन्न करता है?
    • email फ़ील्ड को अनुरोध के अनुसार null पर रीसेट कर दिया जाता है।
    • रनटाइम पर एक अपवाद फेंका जाता है।
    • ऑब्जेक्ट अपना मौजूदा email बनाए रखता है: null ?? this.email मौजूदा वैल्यू पर वापस चला जाता है।
    • स्ट्रिक्ट मोड में कोड कंपाइल नहीं होता।
  3. किसी अपरिवर्तनीय मॉडल पर == और hashCode को फिर से क्यों परिभाषित करें?
    • क्योंकि Dart डिफ़ॉल्ट रूप से रेफ़रेंस की तुलना करता है: अन्यथा फ़ील्ड-दर-फ़ील्ड एक जैसे दो ऑब्जेक्ट "अलग" होते।
    • const इंस्टेंस के निर्माण को तेज़ करने के लिए।
    • जैसे ही कोई फ़ील्ड final घोषित की जाती है, यह अनिवार्य हो जाता है।