Verwandle JSON in unveränderliche Dart-Klassen mit fromJson, toJson, copyWith und zuverlässiger Wertgleichheit.
Diese Lektion in Kodokon öffnenAuf diesem Niveau geht es nicht mehr um die Syntax von Klassen, sondern um die Grenze zwischen der Außenwelt und deinem Code. Solange eine API-Antwort als Map<String, dynamic> durch die App wandert, ist jeder Feldzugriff ein Glücksspiel zur Laufzeit. Die professionelle Regel: Wandle JSON sofort beim Empfang in typisierte Objekte um und reiche danach nur noch diese Objekte weiter. Kombiniere das mit Unveränderlichkeit - final-Felder, const-Konstruktor - und du eliminierst eine ganze Familie von Bugs: Niemand kann mehr ein geteiltes Objekt hinter deinem Rücken verändern, und zwei identische const-Instanzen werden vom Compiler kanonisiert.
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;
}Drei Entscheidungen verdienen eine Erklärung. Erstens (json['id'] as num).toInt(): Viele APIs serialisieren eine Ganzzahl mal als 3, mal als 3.0, je nach Backend; ein direkter Cast auf int stürzt bei einem double ab. Zweitens schlägt as String sofort mit einem klaren TypeError fehl, wenn das Feld fehlt: Das ist Absicht. Ein schnelles Scheitern beim Parsen ist besser als ein null, das sich weiterträgt und drei Bildschirme später explodiert. Und drittens dokumentiert as String? im Typ, dass das Feld wirklich optional ist.
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,
);
}
}Unveränderlichkeit verlangt nach Wertgleichheit. Standardmäßig vergleicht Dart Referenzen: Zwei Feld für Feld identische User gelten als "verschieden". Das ist ein konkretes Problem, sobald du Zustände vergleichst - in Tests, in Providers select, bei der Suche in einer Liste. Definiere == und hashCode immer gemeinsam neu.
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() statt json['id'] as int?as int zur Laufzeit langsamer ist als as num.3.0 serialisieren kann: Ein direkter Cast auf int würde bei einem double fehlschlagen.json['id'] immer eine Zeichenkette ist.user.copyWith(email: null) mit der klassischen copyWith-Implementierung?email wird wie gewünscht auf null zurückgesetzt.email: null ?? this.email fällt auf den bestehenden Wert zurück.== und hashCode an einem unveränderlichen Modell neu definieren?const-Instanzen zu beschleunigen.final deklariert ist.