Flutter 自定义 build_runner 的 build
背景
在使用 json_serializable 库的过程,感觉还是要写很多的重复代码,然后去 github 搜索了一波有没有简单的工具可以替代我这些重复工作的过程,一找还发现真有: json_model。但是在使用这个的过程中,发现这个库是先根据json 生成类,然后在把 dart-lang/build 库里面很多代码搬过来使用 json_serializable 来生成对应的 json 序列化类。我觉得这种方式并不优雅,因为如果 json_serializable 升级了,那么 json_model 对于的代码也需要升级,而且这个库本来可以不依赖于 json_serializable 库的。
探索
于是我去看了 dart-lang/build 的文档,发现是可以自定义 build_runner 的。而且可以订阅 build_runner 中各个 build 间的依赖关系,那应该可以先利用一个 build 将json 转换成 Mode 类,然后在利用 json_serializable 生成 json 序列化类。
实施
- 引入依赖; 必须要依赖于 build 和 build_runner。
dependencies:
build: ">=1.0.0 <1.2.0"
dev_dependencies:
build_runner: ^1.3.1
- 因为我是直接把 json 文件转换为 dart 文件,在 json 文件中不能使用注解。因此直接写了一个类继承于 Builder 。
// file json_builder.dart
import 'dart:async';
import 'dart:convert';
import 'package:build/build.dart';
import './create_template_class.dart';
class JsonBuilder implements Builder {
final CreateTemplateClass _createTemplateClass = CreateTemplateClass();
@override
Future build(BuildStep buildStep) async {
var inputId = buildStep.inputId;
var originalContents = await buildStep.readAsString(inputId);
var copy = inputId.changeExtension('.dart');
Map<String, dynamic> jsonInfo;
try {
jsonInfo = json.decode(originalContents) as Map<String, dynamic>;
} catch (e) {
log.warning('json decode json failed, please checked json file');
return;
}
var content = _createTemplateClass.createContent(inputId.path, jsonInfo);
await buildStep.writeAsString(copy, content);
}
@override
final buildExtensions = const {
'.json': ['.dart']
};
}
// file create_template_class.dart
import 'package:path/path.dart' as path;
typedef String HandlerFun(dynamic v);
class CreateTemplateClass {
final String withPrefiex = '@with';
final String extendsPrefix = '@extends';
final String importPrefix = '@import';
Map<String, HandlerFun> _typeHandlers;
CreateTemplateClass() {
_typeHandlers = {
importPrefix: (imports) {
if (imports is List) {
return imports
.map((v) => "import '$v';")
.join("\r\n");
}
return "import '$imports';";
},
extendsPrefix: (v) => "extends $v",
withPrefiex: (v) => (v is List) ? 'with ${v.join(" ")}' : "with $v"
};
}
String pascalClassName(String fileName) {
return fileName?.split(RegExp(r'_|-'))?.map((word) => '${word[0].toUpperCase()}${word.substring(1)?.toLowerCase()}')?.join();
}
String handlerFiled(key, v) {
if (v is String) {
return (
"""
final $v $key;
"""
);
} else {
Map<String, dynamic> fieldInfo = v as Map<String, dynamic>;
return (
"""
${fieldInfo['pre'] ?? ''}
final ${fieldInfo['type']} $key;
"""
);
}
}
String createDartClass({
String name,
String import,
String className,
String attrs,
String extendClass,
String withClass,
List<String> fieldNames,
}) {
return """
// GENERATED CODE - DO NOT MODIFY BY HAND
// **************************************************************************
// JsonModel Builder
// **
import 'package:json_annotation/json_annotation.dart';
$import
part '$name.g.dart';
@JsonSerializable()
class $className $extendClass $withClass{
$className({
${fieldNames.map((field) => 'this.$field').join(',')}
});
$attrs
factory $className.fromJson(Map<String,dynamic> json) => _\$${className}FromJson(json);
Map<String, dynamic> toJson() => _\$${className}ToJson(this);
}""";
}
// 根据Json生成Dart Class
String createContent(String filePath, Map<String, dynamic> classConfigs) {
var paths = path.basename(filePath).split(".");
String name=paths.first;
StringBuffer attrs = StringBuffer();
var classInfos = {
withPrefiex: '',
importPrefix: '',
extendsPrefix: '',
};
var fieldNames = List<String>();
classConfigs.forEach((key, v) {
if (_typeHandlers.containsKey(key)) {
classInfos[key] = _typeHandlers[key](v);
} else {
attrs.write(handlerFiled(key, v));
fieldNames.add(key);
}
});
return createDartClass(
name: name,
import: classInfos[importPrefix],
className: pascalClassName(name),
attrs: attrs.toString(),
extendClass: classInfos[extendsPrefix],
withClass: classInfos[withPrefiex],
fieldNames: fieldNames,
);
}
}
- 导出 Builder
// file builder.dart
import 'package:build/build.dart';
import 'json_builder.dart';
Builder jsonBuilder(BuilderOptions options) => JsonBuilder();
- 添加 build.yml 文件
builders:
jsonBuilder:
import: "package:json_to_dart_model/builder.dart"
builder_factories: ["jsonBuilder"]
build_extensions: {".json": ["**.dart"]}
build_to: source
auto_apply: root_package
runs_before: ["json_serializable|json_serializable"]
defaults:
generate_for:
# Only apply files in the lib/model_jsons directory
include:
- lib/model/**