5.2 JSON 与序列化
·
Flutter/Dart 没有运行时反射,JSON 序列化需要手动映射或借助代码生成工具。推荐使用
json_serializable或freezed实现类型安全的序列化。
一、手动序列化
1.1 基本模式
class User {
final int id;
final String name;
final String email;
final DateTime createdAt;
final Address? address; // 嵌套对象
const User({
required this.id,
required this.name,
required this.email,
required this.createdAt,
this.address,
});
factory User.fromJson(Map<String, dynamic> json) => User(
id: json['id'] as int,
name: json['name'] as String,
email: json['email'] as String,
createdAt: DateTime.parse(json['created_at'] as String),
address: json['address'] != null
? Address.fromJson(json['address'] as Map<String, dynamic>)
: null,
);
Map<String, dynamic> toJson() => {
'id': id,
'name': name,
'email': email,
'created_at': createdAt.toIso8601String(),
if (address != null) 'address': address!.toJson(),
};
}
缺点:
- 大量重复代码
- 容易手误(字段名拼错)
- 不支持
copyWith、==、hashCode
二、json_serializable(自动代码生成)
dependencies:
json_annotation: ^4.8.1
dev_dependencies:
json_serializable: ^6.7.1
build_runner: ^2.4.0
2.1 定义模型
import 'package:json_annotation/json_annotation.dart';
part 'product.g.dart'; // 生成文件
(
explicitToJson: true, // 嵌套对象也调用 toJson
fieldRename: FieldRename.snake, // 自动 camelCase → snake_case
)
class Product {
final int id;
final String name;
final double price;
(name: 'image_urls') // 自定义字段名映射
final List<String> imageUrls;
(fromJson: _dateFromJson, toJson: _dateToJson)
final DateTime publishedAt;
(defaultValue: true) // 字段缺失时使用默认值
final bool isActive;
(includeFromJson: false, includeToJson: false)
final String? localNote; // 本地字段,不参与序列化
const Product({
required this.id,
required this.name,
required this.price,
required this.imageUrls,
required this.publishedAt,
this.isActive = true,
this.localNote,
});
// 生成的工厂方法
factory Product.fromJson(Map<String, dynamic> json) =>
_$ProductFromJson(json);
Map<String, dynamic> toJson() => _$ProductToJson(this);
// 自定义日期解析
static DateTime _dateFromJson(String date) => DateTime.parse(date);
static String _dateToJson(DateTime date) => date.toIso8601String();
}
2.2 运行代码生成
# 一次性生成
dart run build_runner build --delete-conflicting-outputs
# 监听模式(开发时推荐)
dart run build_runner watch --delete-conflicting-outputs
三、freezed(不可变模型 + 联合类型)
freezed 是功能最强的模型生成库,支持不可变数据类、copyWith、union 类型。
dependencies:
freezed_annotation: ^2.4.1
json_annotation: ^4.8.1
dev_dependencies:
freezed: ^2.4.7
json_serializable: ^6.7.1
build_runner: ^2.4.0
3.1 不可变数据类
import 'package:freezed_annotation/freezed_annotation.dart';
part 'user.freezed.dart';
part 'user.g.dart';
class User with _$User {
const factory User({
required int id,
required String name,
required String email,
([]) List<String> roles,
(false) bool isAdmin,
String? avatarUrl,
(name: 'created_at') required DateTime createdAt,
}) = _User;
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
}
// freezed 自动生成:
// ✅ copyWith
// ✅ == / hashCode
// ✅ toString
// ✅ fromJson / toJson
// 使用 copyWith
final updatedUser = user.copyWith(name: 'New Name', isAdmin: true);
3.2 Union 类型(密封类/Result 模式)
class ApiResult<T> with _$ApiResult<T> {
const factory ApiResult.loading() = _Loading;
const factory ApiResult.success(T data) = _Success;
const factory ApiResult.error(String message, {int? code}) = _Error;
}
// 使用 when 处理所有情况
Widget buildFromResult(ApiResult<List<Product>> result) {
return result.when(
loading: () => const CircularProgressIndicator(),
success: (products) => ProductGrid(products: products),
error: (message, code) => ErrorWidget(message: message),
);
}
// maybeWhen:只处理部分情况
result.maybeWhen(
success: (data) => showData(data),
orElse: () => showPlaceholder(),
)
// map:转换
final displayString = result.map(
loading: (_) => 'Loading...',
success: (s) => '${s.data.length} items',
error: (e) => 'Error: ${e.message}',
);
四、手动 vs 自动对比
| 功能 | 手动 | json_serializable | freezed |
|---|---|---|---|
| JSON 序列化 | ✅ | ✅(生成) | ✅(生成) |
| copyWith | ❌ 需手写 | ❌ | ✅ 自动 |
| == / hashCode | ❌ 需手写 | ❌ | ✅ 自动 |
| toString | ❌ 需手写 | ❌ | ✅ 自动 |
| Union 类型 | ❌ | ❌ | ✅ |
| 不可变性 | 手动 const | 手动 final | 强制不可变 |
| 适用场景 | 极简模型 | 中型项目 | 中大型项目 |
五、API 响应统一包装
class ApiResponse<T> with _$ApiResponse<T> {
const factory ApiResponse({
required int code,
required String message,
T? data,
([]) List<ValidationError> errors,
}) = _ApiResponse;
factory ApiResponse.fromJson(
Map<String, dynamic> json,
T Function(Object?) fromJsonT,
) => _$ApiResponseFromJson(json, fromJsonT);
}
// 使用泛型解析
final response = ApiResponse<List<Product>>.fromJson(
jsonData,
(json) => (json as List).map((e) => Product.fromJson(e)).toList(),
);
小结
| 推荐方案 | 选择依据 |
|---|---|
| 手动 | 模型极少/临时代码 |
| json_serializable | 普通数据模型,中型项目 |
| freezed | 需要不可变、copyWith、Union 类型,推荐大型项目 |
👉 下一节:5.3 本地存储
更多推荐
所有评论(0)