关于 flutter-implement-json-serialization
面向 Dart 和 Flutter 开发者的 JSON 序列化指南,介绍类型安全的模型转换、后台解析,以及手动实现与代码生成的选择方法。
主要功能
- 01实现 fromJson 和 toJson
- 02使用模式匹配解析 JSON
- 03在后台 isolate 解析大型数据
- 04比较手动序列化与代码生成
- 05通过工作流验证序列化结果
适用场景
- 01为模型添加 JSON 转换方法
- 02解析嵌套 JSON 对象
- 03将大型响应移至后台解析
- 04决定采用手动或生成式序列化
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ dhruvanbhalara/ skills --skill flutter-implement-json-serialization安装后在 Claude Code 中输入/flutter-implement-json-serialization确认已加载。
详细介绍
目录
核心指南
- 导入
dart:convert:手动序列化时使用jsonEncode()和jsonDecode()。 - 类型安全:始终将
jsonDecode()的结果强制转换为Map<String, dynamic>(对象)或List<dynamic>(数组)。切勿直接使用原始dynamic。 - 封装:在模型类中定义
fromJson工厂构造函数和toJson方法。 - 后台解析:如果解析耗时超过 16ms(大型 JSON 负载),则通过
compute()将其移至单独的 isolate。 - 错误处理:遇到无效 JSON 时抛出
FormatException。切勿从fromJson返回null。
在 fromJson 中使用 Dart 3 模式匹配
使用带解构的 switch 表达式,实现类型安全且简洁的反序列化:
import 'dart:convert';
class User {
final int id;
final String name;
final String email;
const User({required this.id, required this.name, required this.email});
factory User.fromJson(Map<String, dynamic> json) {
return switch (json) {
{
'id': final int id,
'name': final String name,
'email': final String email,
} =>
User(id: id, name: name, email: email),
_ => throw const FormatException('Invalid User JSON'),
};
}
Map<String, dynamic> toJson() => {
'id': id,
'name': name,
'email': email,
};
}
相较于手动强制转换的优势:
- 解构模式可在编译时进行类型检查
- 单个表达式即可同时完成提取和验证
- 类型不匹配时会自动抛出
FormatException
嵌套对象
class Post {
final int id;
final String title;
final User author;
const Post({required this.id, required this.title, required this.author});
factory Post.fromJson(Map<String, dynamic> json) {
return switch (json) {
{
'id': final int id,
'title': final String title,
'author': final Map<String, dynamic> authorJson,
} =>
Post(id: id, title: title, author: User.fromJson(authorJson)),
_ => throw const FormatException('Invalid Post JSON'),
};
}
Map<String, dynamic> toJson() => {
'id': id,
'title': title,
'author': author.toJson(),
};
}
后台解析
对于大型 JSON 负载(包含数千个对象),请将解析移至后台 isolate:
import 'dart:convert';
import 'package:flutter/foundation.dart';
// MUST be a top-level function (not a method or closure)
List<User> parseUsers(String responseBody) {
final parsed = (jsonDecode(responseBody) as List<dynamic>)
.cast<Map<String, dynamic>>();
return parsed.map<User>((json) => User.fromJson(json)).toList();
}
Future<List<User>> fetchUsers(http.Client client) async {
final response = await client.get(Uri.parse('https://api.example.com/users'));
if (response.statusCode == 200) {
return compute(parseUsers, response.body);
} else {
throw Exception('Failed to load users: ${response.statusCode}');
}
}
规则:
- 解析函数必须是顶层函数或静态方法(不能是实例方法)。
- 简单的 isolate 任务使用
compute()。对于复杂场景,使用Isolate.run()。 - 阈值:解析超过 10,000 个项目或负载超过 1MB → 使用后台 isolate。
手动实现与代码生成的选择
| 标准 | 手动实现(dart:convert) | 代码生成(json_serializable / freezed) |
|---|---|---|
| 模型数量 | 少于 5 个模型 | 多于 5 个模型 |
| 嵌套深度 | 较浅(1-2 层) | 深层 / 复杂层级结构 |
| 开发依赖 | 无 | build_runner、json_annotation |
| 类型安全 | Dart 3 模式匹配 | 生成的代码 |
| 样板代码 | 每个模型手动编写 | 自动生成 |
| 构建时间 | 无 | 增加 build_runner 步骤 |
| 灵活性 | 完全掌控解析过程 | 受注解限制 |
建议:原型和小型模型从手动序列化开始。当模型数量超过 5 个或嵌套变得复杂时,迁移到 json_serializable 或 freezed。代码生成工作流请参阅 flutter-code-gen 技能。
工作流:实现可序列化模型
任务进度
- 步骤 1:定义模型类,包含
final属性和const构造函数。 - 步骤 2:使用模式匹配实现
factory Model.fromJson(Map<String, dynamic> json)。 - 步骤 3:实现
Map<String, dynamic> toJson()方法。 - 步骤 4:编写序列化往返单元测试(
fromJson(toJson(model)) == model)。 - 步骤 5:运行测试:
dart test或flutter test。 - 步骤 6:反馈循环:修复类型不匹配错误 → 重新运行,直到全部通过。
工作流:获取并解析 JSON
任务进度
- 步骤 1:执行 HTTP 请求。
- 步骤 2:验证响应状态码(200 → 继续,否则 → 抛出异常)。
- 步骤 3:确定解析策略:
- 小型负载 → 在主线程上同步解析。
- 大型负载 → 使用
compute(parseFunction, response.body)。
- 步骤 4:解码 JSON,并通过
fromJson将其映射到模型。
示例
同步 HTTP 获取与解析
import 'dart:convert';
import 'package:http/http.dart' as http;
Future<User> fetchUser(http.Client client, int userId) async {
final response = await client.get(
Uri.parse('https://api.example.com/users/$userId'),
headers: {'Accept': 'application/json'},
);
if (response.statusCode == 200) {
final Map<String, dynamic> jsonMap =
jsonDecode(response.body) as Map<String, dynamic>;
return User.fromJson(jsonMap);
} else {
throw Exception('Failed to load user: ${response.statusCode}');
}
}
类型安全的列表解析
Future<List<User>> fetchAllUsers(http.Client client) async {
final response = await client.get(
Uri.parse('https://api.example.com/users'),
);
if (response.statusCode == 200) {
final List<dynamic> jsonList = jsonDecode(response.body) as List<dynamic>;
return jsonList
.map((e) => User.fromJson(e as Map<String, dynamic>))
.toList();
} else {
throw Exception('Failed to load users');
}
}
序列化单元测试
import 'package:flutter_test/flutter_test.dart';
void main() {
group('$User', () {
test('fromJson creates valid User', () {
final json = {'id': 1, 'name': 'Alice', 'email': '[email protected]'};
final user = User.fromJson(json);
expect(user.id, 1);
expect(user.name, 'Alice');
expect(user.email, '[email protected]');
});
test('toJson returns valid map', () {
const user = User(id: 1, name: 'Alice', email: '[email protected]');
final json = user.toJson();
expect(json['id'], 1);
expect(json['name'], 'Alice');
});
test('round-trip serialization', () {
const original = User(id: 1, name: 'Alice', email: '[email protected]');
final json = original.toJson();
final restored = User.fromJson(json);
expect(restored.id, original.id);
expect(restored.name, original.name);
expect(restored.email, original.email);
});
test('throws FormatException on invalid JSON', () {
final invalidJson = {'id': 'not_an_int', 'name': 'Alice'};
expect(() => User.fromJson(invalidJson), throwsFormatException);
});
});
}
评论
支持 Markdown