将 JSON 转换为 Dart 模型类是 Flutter 开发中最常见的任务之一。无论是从 REST API 获取数据还是读取本地配置,你都需要结构良好的 Dart 类来安全地解析和使用 JSON 数据。
为什么需要模型类
- 类型安全 — 在编译时而非运行时捕获错误
- IDE 自动补全和重构支持
- 前后端之间清晰的数据契约
- 更容易测试和维护
基本 JSON 到 Dart 类型映射
| JSON 类型 | Dart 类型 | 示例 |
|---|---|---|
| string | String | "hello" → "hello" |
| number (int) | int | 42 → 42 |
| number (float) | double | 3.14 → 3.14 |
| boolean | bool | true → true |
| null | Null / dynamic | null → null |
| array | List<T> | [1,2,3] → [1,2,3] |
| object | Map / Class | {} → User() |
手动模型类(fromJson / toJson)
最直接的方法是手写模型类。以下是完整示例:
// JSON input
{
"id": 1,
"name": "John Doe",
"email": "john@example.com",
"isActive": true,
"score": 95.5
}
// Dart model class
class User {
final int id;
final String name;
final String email;
final bool isActive;
final double score;
User({
required this.id,
required this.name,
required this.email,
required this.isActive,
required this.score,
});
factory User.fromJson(Map<String, dynamic> json) {
return User(
id: json['id'] as int,
name: json['name'] as String,
email: json['email'] as String,
isActive: json['isActive'] as bool,
score: (json['score'] as num).toDouble(),
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'email': email,
'isActive': isActive,
'score': score,
};
}
}处理嵌套对象
当 JSON 包含嵌套对象时,为每一层创建单独的 Dart 类:
// JSON with nested object
{
"id": 1,
"name": "John",
"address": {
"street": "123 Main St",
"city": "Springfield",
"zipCode": "62701"
}
}
// Dart classes
class Address {
final String street;
final String city;
final String zipCode;
Address({required this.street, required this.city, required this.zipCode});
factory Address.fromJson(Map<String, dynamic> json) {
return Address(
street: json['street'] as String,
city: json['city'] as String,
zipCode: json['zipCode'] as String,
);
}
Map<String, dynamic> toJson() => {
'street': street, 'city': city, 'zipCode': zipCode,
};
}
class User {
final int id;
final String name;
final Address address;
User({required this.id, required this.name, required this.address});
factory User.fromJson(Map<String, dynamic> json) {
return User(
id: json['id'] as int,
name: json['name'] as String,
address: Address.fromJson(json['address'] as Map<String, dynamic>),
);
}
Map<String, dynamic> toJson() => {
'id': id, 'name': name, 'address': address.toJson(),
};
}处理列表和数组
JSON 数组映射为 Dart 的 List 类型。处理简单和复杂数组:
// JSON with arrays
{
"name": "John",
"hobbies": ["coding", "reading", "gaming"],
"orders": [
{"id": 1, "product": "Laptop", "price": 999.99},
{"id": 2, "product": "Mouse", "price": 29.99}
]
}
// Dart
class User {
final String name;
final List<String> hobbies;
final List<Order> orders;
User({required this.name, required this.hobbies, required this.orders});
factory User.fromJson(Map<String, dynamic> json) {
return User(
name: json['name'] as String,
hobbies: List<String>.from(json['hobbies'] as List),
orders: (json['orders'] as List)
.map((e) => Order.fromJson(e as Map<String, dynamic>))
.toList(),
);
}
}空安全最佳实践
使用 Dart 的健全空安全,你需要正确处理可空字段:
- 对必须存在的字段使用 `required` 关键字
- 对可空类型使用 `?` 后缀(如 `String?`)
- 在 fromJson 中使用 `??` 运算符提供默认值
- 仅在确定值会被初始化时使用 `late` 关键字
class User {
final int id;
final String name;
final String? bio; // nullable
final String avatarUrl; // with default
User({
required this.id,
required this.name,
this.bio,
this.avatarUrl = 'https://example.com/default-avatar.png',
});
factory User.fromJson(Map<String, dynamic> json) {
return User(
id: json['id'] as int,
name: json['name'] as String,
bio: json['bio'] as String?,
avatarUrl: json['avatarUrl'] as String? ?? 'https://example.com/default-avatar.png',
);
}
}使用 json_serializable 包
对于大型项目,json_serializable 包可以自动化模板代码:
步骤 1:添加依赖
# pubspec.yaml
dependencies:
json_annotation: ^4.8.1
dev_dependencies:
json_serializable: ^6.7.1
build_runner: ^2.4.6步骤 2:创建带注解的模型
import 'package:json_annotation/json_annotation.dart';
part 'user.g.dart';
@JsonSerializable()
class User {
final int id;
final String name;
final String email;
@JsonKey(name: 'is_active')
final bool isActive;
@JsonKey(defaultValue: 0.0)
final double score;
User({
required this.id,
required this.name,
required this.email,
required this.isActive,
required this.score,
});
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
Map<String, dynamic> toJson() => _$UserToJson(this);
}步骤 3:运行 Build Runner
dart run build_runner build --delete-conflicting-outputs使用 Freezed 创建不可变模型
freezed 包生成带有 copyWith、相等性和 JSON 序列化的不可变类:
import 'package:freezed_annotation/freezed_annotation.dart';
part 'user.freezed.dart';
part 'user.g.dart';
@freezed
class User with _$User {
const factory User({
required int id,
required String name,
required String email,
@Default(false) bool isActive,
}) = _User;
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
}常见错误与修复
| 错误 | 修复 |
|---|---|
| 使用 `json['key']` 但不检查 null | 使用 `json['key'] as String? ?? ''` 或处理 null |
| 忘记转换嵌套对象 | 对对象调用 `NestedClass.fromJson(json['key'])` |
| 所有数字都使用 `int` | 使用 `num` 并转换:`(json['price'] as num).toDouble()` |
| 不处理缺失的键 | 使用 `json.containsKey('key')` 或提供默认值 |
| toJson 返回错误类型 | 确保嵌套对象调用自己的 `.toJson()` |
常见问题
将 JSON 转换为 Dart 类的最佳方法是什么?
小项目手动转换即可。大型项目使用 json_serializable 或 freezed 包自动生成模板代码。在线工具如我们的 JSON to Dart 转换器可以帮助生成初始代码。
如何处理 Dart 中的动态 JSON 键?
对未知键的对象使用 Map<String, dynamic>。例如:final Map<String, dynamic> metadata = json['metadata'] as Map<String, dynamic>;
应该使用 json_serializable 还是 freezed?
简单 JSON 序列化用 json_serializable。如果还需要不可变性、copyWith、联合类型和密封类,用 freezed。Freezed 内部使用 json_serializable 进行 JSON 支持。
如何处理 JSON 到 Dart 转换中的日期?
JSON 没有原生日期类型。用 DateTime.parse(json['date']) 解析 ISO 8601 字符串。在 toJson 中使用 dateTime.toIso8601String()。