JSON to Dart
Generování Dart tříd z JSON s fromJson a toJson
Vstup JSON
Výstup Dart
Co je převod JSON na Dart?
Převod JSON na Dart vezme surový JSON objekt a vytvoří definice Dart tříd s typovanými poli, pojmenovaným konstruktorem, továrnou fromJson a metodou toJson. Dart nemá v Flutteru runtime reflexi (dart:mirrors je zakázán), takže nelze deserializovat JSON do typovaných objektů bez explicitního mapovacího kódu. Každá REST API odpověď, Firestore dokument nebo konfigurační payload potřebuje odpovídající Dart třídu, než lze k polím přistupovat s typovou bezpečností.
Typická Dart třída modelu pro JSON deklaruje final pole pro každý klíč, konstruktor s pojmenovanými parametry (klíčové slovo required pro non-nullable pole), tovární konstruktor fromJson čtoucí z Map<String, dynamic> a metodu toJson vracející Map<String, dynamic>. Vnořené JSON objekty se stávají samostatnými třídami. Pole se stávají typovanými poli List<T>. Nullable JSON hodnoty používají syntaxi null-safety s příponou ? za typem.
Ruční psaní těchto tříd modelů znamená číst každý JSON klíč, rozhodovat o Dart typu, vytvářet fromJson přetypování pro každé pole (včetně mapování seznamů přes .map().toList()), sestavovat mapový literál toJson a opakovat vše pro každý vnořený objekt. Pro JSON objekt s 12 poli a 2 vnořenými objekty to znamená 3 třídy, 6 továrních řádků a desítky přetypovacích výrazů. Převodník vše vyprodukuje v milisekundách z jediného vložení.
Proč používat převodník JSON na Dart?
Ruční vytváření Dart tříd modelů z JSON zahrnuje čtení názvů polí, odvozování typů z ukázkových hodnot, psaní fromJson přetypování se správným ošetřením null a opakování celého procesu pro vnořené objekty. Když se tvar API změní, každá aktualizace pole se dotkne konstruktoru, fromJson i toJson. Převodník tuto opakující se práci odstraní.
Případy použití JSON na Dart
Mapování typů JSON na Dart
Každá JSON hodnota se mapuje na konkrétní Dart typ. Tabulka níže ukazuje, jak převodník překládá každý JSON typ. Sloupec Alternativa zobrazuje typy používané v méně obvyklých nebo ručních mapovacích scénářích.
| Typ JSON | Příklad | Typ Dart | Alternativa |
|---|---|---|---|
| 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> |
Přístupy k JSON serializaci v Dartu
Dart a Flutter nabízejí více způsobů zpracování JSON serializace. Ruční fromJson/toJson je nejjednodušší přístup a nevyžaduje generování kódu. Pro větší projekty řešení založená na build_runner, jako json_serializable a freezed, generují šablonový kód při kompilaci, snižují chyby a udržují modely konzistentní s JSON kontraktem.
| Přístup | Popis | Zdroj |
|---|---|---|
| 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 |
Ruční vs json_serializable vs freezed
Dart má tři běžné přístupy pro JSON třídy modelů. Ruční fromJson/toJson nemá žádné závislosti. json_serializable automatizuje mapovací kód. freezed přidává neměnitelnost, copyWith a porovnávání vzorů na základě json_serializable.
Příklady kódu
Tyto příklady ukazují, jak používat generované Dart třídy pro deserializaci JSON, jak nastavit json_serializable s build_runner a jak programově generovat Dart třídy z JavaScriptu a Pythonu.
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;
# }