Transformez le JSON en classes Dart immuables avec fromJson, toJson, copyWith et une égalité de valeur fiable.
Ouvrir cette leçon dans KodokonÀ ce niveau, le vrai sujet n'est plus la syntaxe des classes mais la frontière entre le monde extérieur et votre code. Tant qu'une réponse d'API circule sous forme de Map<String, dynamic>, chaque lecture de champ est un pari au runtime. La règle du métier : convertir le JSON en objets typés dès la réception, puis ne faire circuler que ces objets. Combinez cela avec l'immutabilité - champs final, constructeur const - et vous éliminez toute une famille de bugs : personne ne peut plus modifier un objet partagé dans votre dos, et deux instances const identiques sont canonicalisées par le compilateur.
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;
}Trois choix méritent d'être explicités. D'abord (json['id'] as num).toInt() : beaucoup d'API sérialisent un entier tantôt en 3, tantôt en 3.0 selon le backend ; un cast direct en int plante sur un double. Ensuite, as String échoue immédiatement avec une TypeError claire si le champ manque : c'est voulu. Échouer vite au parsing vaut mieux qu'un null qui se propage et explose trois écrans plus loin. Enfin, as String? documente dans le type que le champ est réellement optionnel.
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,
);
}
}L'immutabilité appelle l'égalité de valeur. Par défaut, Dart compare les références : deux User identiques champ à champ sont considérés « différents ». C'est un problème concret dès que vous comparez des états - dans les tests, dans le select de Provider, dans une recherche de liste. Redéfinissez == et hashCode ensemble, toujours.
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() plutôt que json['id'] as int ?as int est plus lent que as num à l'exécution.3.0 : le cast direct en int échouerait sur un double.json['id'] est toujours une chaîne de caractères.user.copyWith(email: null) avec l'implémentation classique de copyWith ?email est remis à null comme demandé.email actuel : null ?? this.email retombe sur la valeur existante.== et hashCode sur un modèle immuable ?const.final.