JSON to Dart
Dart-Klassen aus JSON mit fromJson und toJson generieren
JSON-Eingabe
Dart-Ausgabe
Was ist die JSON-zu-Dart-Konvertierung?
Die JSON-zu-Dart-Konvertierung nimmt ein rohes JSON-Objekt und erzeugt daraus Dart-Klassendefinitionen mit typisierten Feldern, einem benannten Konstruktor, einer fromJson-Factory und einer toJson-Methode. Dart verfügt in Flutter über keine Laufzeit-Reflection (dart:mirrors ist deaktiviert), daher können JSON-Daten nicht ohne expliziten Mapping-Code in typisierte Objekte deserialisiert werden. Jede REST-API-Antwort, jedes Firestore-Dokument und jeder Konfigurations-Payload benötigt eine entsprechende Dart-Modellklasse, bevor man typsicher auf die Felder zugreifen kann.
Eine typische Dart-Modellklasse für JSON deklariert finale Felder für jeden Schlüssel, einen Konstruktor mit benannten Parametern (mit dem required-Schlüsselwort für nicht-nullable Felder), eine Factory-Methode namens fromJson, die aus einer Map<String, dynamic> liest, sowie eine toJson-Methode, die eine Map<String, dynamic> zurückgibt. Verschachtelte JSON-Objekte werden zu separaten Klassen. Arrays werden zu typisierten List-Feldern. Nullable JSON-Werte verwenden Darts Null-Safety-Syntax mit dem ?-Suffix am Typ.
Diese Modellklassen von Hand zu schreiben bedeutet: jeden JSON-Schlüssel lesen, den Dart-Typ festlegen, den fromJson-Cast für jedes Feld erstellen (einschließlich Listen-Mapping mit .map().toList()), ein toJson-Map-Literal erstellen und das Ganze für jedes verschachtelte Objekt wiederholen. Bei einem JSON-Objekt mit 12 Feldern und 2 verschachtelten Objekten entstehen 3 Klassen, 6 Factory-Zeilen und dutzende Cast-Ausdrücke. Ein Konverter erzeugt all das in Millisekunden aus einem einzigen Einfügevorgang.
Warum einen JSON-zu-Dart-Konverter verwenden?
Dart-Modellklassen manuell aus JSON zu erstellen bedeutet: Feldnamen lesen, Typen aus Beispielwerten ableiten, fromJson-Casts mit korrekter Null-Behandlung schreiben und diesen Prozess für verschachtelte Objekte wiederholen. Ändert sich die API-Struktur, muss jede Feldaktualisierung im Konstruktor, in fromJson und in toJson angepasst werden. Ein Konverter nimmt diese repetitive Arbeit ab.
JSON-zu-Dart-Anwendungsfälle
JSON-zu-Dart-Typ-Mapping
Jeder JSON-Wert wird auf einen bestimmten Dart-Typ abgebildet. Die folgende Tabelle zeigt, wie der Konverter jeden JSON-Typ übersetzt. Die Spalte Alternative zeigt Typen, die in selteneren oder manuellen Mapping-Szenarien verwendet werden.
| JSON-Typ | Beispiel | Dart-Typ | Alternative |
|---|---|---|---|
| string | "hello" | String | String |
| number (integer) | 42 | int | int |
| number (float) | 3.14 | double | double |
| boolean | true | bool | bool |
| null | null | dynamic | Null / dynamic |
| object | {"k": "v"} | NestedClass | Map<String, dynamic> |
| array of strings | ["a", "b"] | List<String> | List<String> |
| array of objects | [{"id": 1}] | List<Item> | List<Item> |
| mixed array | [1, "a"] | List<dynamic> | List<dynamic> |
Dart-JSON-Serialisierungsansätze
Dart und Flutter bieten mehrere Möglichkeiten zur JSON-Serialisierung. Manuelles fromJson/toJson ist der einfachste Ansatz und erfordert keine Code-Generierung. Für größere Projekte erzeugen build_runner-basierte Lösungen wie json_serializable und freezed den Boilerplate zur Compile-Zeit, reduzieren Fehler und halten die Modelle konsistent mit dem JSON-Vertrag.
| Ansatz | Beschreibung | Quelle |
|---|---|---|
| json_serializable | Code-generation-based JSON serialization. Generates .g.dart files at build time via build_runner. Type-safe and compile-time verified. | Official (Google) |
| freezed | Generates immutable data classes with copyWith, fromJson/toJson, equality, and pattern matching support. Built on top of json_serializable. | Community (rrousselGit) |
| built_value | Generates immutable value types with serialization. Enforces immutability patterns. Used in larger codebases with strict data modeling. | |
| dart_mappable | Annotation-based mapper that generates fromJson, toJson, copyWith, and equality. Simpler setup than freezed with similar features. | Community |
| Manual fromJson/toJson | Hand-written factory constructors and toJson methods. No code generation needed. Full control over the mapping logic. | Built-in Dart |
Manuell vs. json_serializable vs. freezed
Dart bietet drei gängige Ansätze für JSON-Modellklassen. Manuelles fromJson/toJson kommt ohne Abhängigkeiten aus. json_serializable automatisiert den Mapping-Code. freezed fügt Unveränderlichkeit, copyWith und Pattern-Matching auf Basis von json_serializable hinzu.
Code-Beispiele
Diese Beispiele zeigen, wie generierte Dart-Klassen für die JSON-Deserialisierung verwendet werden, wie json_serializable mit build_runner eingerichtet wird und wie Dart-Klassen programmatisch aus JavaScript und Python erzeugt werden.
import 'dart:convert';
class User {
final int id;
final String name;
final String email;
final bool active;
final Address address;
final List<String> tags;
User({
required this.id,
required this.name,
required this.email,
required this.active,
required this.address,
required this.tags,
});
factory User.fromJson(Map<String, dynamic> json) => User(
id: json['id'] as int,
name: json['name'] as String,
email: json['email'] as String,
active: json['active'] as bool,
address: Address.fromJson(json['address'] as Map<String, dynamic>),
tags: (json['tags'] as List<dynamic>).map((e) => e as String).toList(),
);
Map<String, dynamic> toJson() => {
'id': id,
'name': name,
'email': email,
'active': active,
'address': address.toJson(),
'tags': tags,
};
}
class Address {
final String street;
final String city;
final String zip;
Address({required this.street, required this.city, required this.zip});
factory Address.fromJson(Map<String, dynamic> json) => Address(
street: json['street'] as String,
city: json['city'] as String,
zip: json['zip'] as String,
);
Map<String, dynamic> toJson() => {'street': street, 'city': city, 'zip': zip};
}
void main() {
final jsonStr = '{"id":1,"name":"Alice","email":"alice@example.com","active":true,"address":{"street":"123 Main","city":"Springfield","zip":"12345"},"tags":["admin","user"]}';
final user = User.fromJson(jsonDecode(jsonStr));
print(user.name); // -> Alice
print(jsonEncode(user.toJson())); // -> round-trip back to JSON
}// pubspec.yaml dependencies:
// json_annotation: ^4.8.0
// dev_dependencies:
// build_runner: ^2.4.0
// json_serializable: ^6.7.0
import 'package:json_annotation/json_annotation.dart';
part 'user.g.dart'; // generated by: dart run build_runner build
@JsonSerializable()
class User {
final int id;
final String name;
final String email;
@JsonKey(name: 'is_active')
final bool isActive;
final List<String> tags;
User({
required this.id,
required this.name,
required this.email,
required this.isActive,
required this.tags,
});
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
Map<String, dynamic> toJson() => _$UserToJson(this);
}
// Run code generation:
// dart run build_runner build --delete-conflicting-outputsfunction jsonToDart(obj, name = "Root") {
const classes = [];
function infer(val, fieldName) {
if (val === null) return "dynamic";
if (typeof val === "string") return "String";
if (typeof val === "number") return Number.isInteger(val) ? "int" : "double";
if (typeof val === "boolean") return "bool";
if (Array.isArray(val)) {
const first = val.find(v => v !== null);
if (!first) return "List<dynamic>";
return `List<${infer(first, fieldName)}>`;
}
if (typeof val === "object") {
const cls = fieldName.charAt(0).toUpperCase() + fieldName.slice(1);
build(val, cls);
return cls;
}
return "dynamic";
}
function build(obj, cls) {
const fields = Object.entries(obj).map(([k, v]) =>
` final ${infer(v, k)} ${k};`
);
classes.push(`class ${cls} {\n${fields.join("\n")}\n}`);
}
build(obj, name);
return classes.join("\n\n");
}
console.log(jsonToDart({ id: 1, name: "Alice", scores: [98, 85] }, "User"));
// class User {
// final int id;
// final String name;
// final List<int> scores;
// }import json
def json_to_dart(obj: dict, class_name: str = "Root") -> str:
classes = []
def infer(val, name):
if val is None:
return "dynamic"
if isinstance(val, bool):
return "bool"
if isinstance(val, int):
return "int"
if isinstance(val, float):
return "double"
if isinstance(val, str):
return "String"
if isinstance(val, list):
if not val:
return "List<dynamic>"
return f"List<{infer(val[0], name)}>"
if isinstance(val, dict):
cls = name[0].upper() + name[1:]
build(val, cls)
return cls
return "dynamic"
def build(obj, cls):
fields = [f" final {infer(v, k)} {k};" for k, v in obj.items()]
classes.append(f"class {cls} {{\n" + "\n".join(fields) + "\n}")
build(obj, class_name)
return "\n\n".join(classes)
data = json.loads('{"id": 1, "name": "Alice", "active": true}')
print(json_to_dart(data, "User"))
# class User {
# final int id;
# final String name;
# final bool active;
# }