JSON do Dart
Generuj klasy Dart z JSON z metodami fromJson i toJson
Wejście JSON
Wyjście Dart
Czym jest konwersja JSON do Dart?
Konwersja JSON do Dart pobiera surowy obiekt JSON i produkuje definicje klas Dart z typowanymi polami, nazwanym konstruktorem, fabryką fromJson i metodą toJson. Dart nie obsługuje refleksji w czasie wykonania we Flutterze (dart:mirrors jest wyłączony), więc nie można deserializować JSON do typowanych obiektów bez jawnego kodu mapującego. Każda odpowiedź REST API, dokument Firebase czy ładunek konfiguracyjny wymaga odpowiedniej klasy modelu Dart, zanim możliwy będzie dostęp do jej pól z bezpieczeństwem typów.
Typowa klasa modelu Dart dla JSON deklaruje pola final dla każdego klucza, konstruktor z nazwanymi parametrami (ze słowem kluczowym required dla pól non-nullable), konstruktor fabryczny fromJson odczytujący z Map<String, dynamic> oraz metodę toJson zwracającą Map<String, dynamic>. Zagnieżdżone obiekty JSON stają się osobnymi klasami. Tablice stają się typowanymi polami List. Wartości nullable w JSON używają składni null-safety Darta z sufiksem ? przy typie.
Ręczne pisanie tych klas modelu wymaga odczytania każdego klucza JSON, ustalenia typu Darta, stworzenia rzutowania fromJson dla każdego pola (łącznie z mapowaniem list przez .map().toList()), zbudowania literału mapy toJson i powtórzenia tego dla każdego zagnieżdżonego obiektu. Dla obiektu JSON z 12 polami i 2 zagnieżdżonymi obiektami oznacza to 3 klasy, 6 linii fabryk i dziesiątki wyrażeń rzutowania. Konwerter produkuje to wszystko w milisekundy z jednego wklejenia.
Dlaczego warto używać konwertera JSON do Dart?
Ręczne pisanie klas modelu Dart z JSON wymaga odczytywania nazw pól, zgadywania typów z przykładowych wartości, pisania rzutowań fromJson z poprawną obsługą null i powtarzania tego procesu dla zagnieżdżonych obiektów. Gdy kształt API się zmienia, każda aktualizacja pola dotyka konstruktora, fromJson i toJson. Konwerter eliminuje tę powtarzalną pracę.
Przypadki użycia konwertera JSON do Dart
Mapowanie typów JSON do Dart
Każda wartość JSON mapuje się na konkretny typ Darta. Poniższa tabela pokazuje, jak konwerter tłumaczy każdy typ JSON. Kolumna Alternatywa pokazuje typy używane w rzadszych lub ręcznych scenariuszach mapowania.
| Typ JSON | Przykład | Typ Dart | Alternatywa |
|---|---|---|---|
| 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> |
Podejścia do serializacji JSON w Darcie
Dart i Flutter oferują wiele sposobów obsługi serializacji JSON. Ręczne fromJson/toJson to najprostsze podejście niewymagające generowania kodu. W większych projektach rozwiązania oparte na build_runner, takie jak json_serializable i freezed, generują szablonowy kod podczas kompilacji, zmniejszając liczbę błędów i utrzymując modele zgodne z kontraktem JSON.
| Podejście | Opis | Źródło |
|---|---|---|
| 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 |
Porównanie: ręcznie, json_serializable, freezed
Dart ma trzy popularne podejścia do klas modelu JSON. Ręczne fromJson/toJson nie ma żadnych zależności. json_serializable automatyzuje kod mapowania. freezed dodaje niezmienność, copyWith i dopasowanie wzorców na bazie json_serializable.
Przykłady kodu
Te przykłady pokazują, jak używać wygenerowanych klas Dart do deserializacji JSON, jak skonfigurować json_serializable z build_runner oraz jak generować klasy Dart programowo z JavaScript i Python.
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;
# }