Ados

a fullstack game worker

0%

每天进步一点点021 - flutter的json转dart model问题

Preface

最近在做一个app,以后续用来找工作可以拿出来看看。
试试自己到产品设计能力,前后端能力等等。
中间遇到到一些有值得记录的点全部记录在此。

Content

json - model

本地 jsonmodel 互转,主要用到了 json_serializiable json_annotation build_runner
参考了Json转Dart Model类

meta版本依赖问题

由于使用的flutter版本是2.2.4,flutter_test与json_annotation同时依赖了不同版本的meta库,所以不得已,没有用上最新的json对应库:

1
2
json_annotation: ^4.0.1,
json_serializable: ^4.1.4

类型拓展

以下是模版,在原版对基础上新增了构造器参数,不然新版本会提醒 null safty 问题。
由于vs code老是提醒有错误,所以这个文件命名为template.tmpl

1
2
3
4
5
6
7
8
9
10
11
import 'package:json_annotation/json_annotation.dart';
%t
part '%s.g.dart';
@JsonSerializable()
class %s {
%s(%c);

%s
factory %s.fromJson(Map<String,dynamic> json) => _$%sFromJson(json);
Map<String, dynamic> toJson() => _$%sToJson(this);
}

以下是生成modeldart程序,新增了构造器参数和复杂类型的支持:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import 'dart:convert';
import 'dart:io';
import 'package:path/path.dart' as path;

const TAG = "\$";
const SRC = "./json"; //JSON 目录
const DIST = "lib/models/"; //输出model目录

void walk() {
//遍历JSON目录生成模板
var src = new Directory(SRC);
var list = src.listSync();
var template = new File("template/template.tmpl").readAsStringSync();
File file;
list.forEach((f) {
if (FileSystemEntity.isFileSync(f.path)) {
file = new File(f.path);
var paths = path.basename(f.path).split(".");
String name = paths.first;
if (paths.last.toLowerCase() != "json" || name.startsWith("_")) return;
if (name.startsWith("_")) return;
//下面生成模板
var map = json.decode(file.readAsStringSync());
//为了避免重复导入相同的包,我们用Set来保存生成的import语句。
var set = new Set<String>();
StringBuffer attrs = new StringBuffer();
List<String> params = [];
(map as Map<String, dynamic>).forEach((key, v) {
if (key.startsWith("_")) return;
attrs.write(getType(v, set, name));
attrs.write(" ");
attrs.write(key);
attrs.writeln(";");
attrs.write(" ");
params.add("required this.$key");
});
//新增的构造器参数部分
String conParams = "{${params.join(",")}}";
String className = name[0].toUpperCase() + name.substring(1);
var dist = format(template, [
name,
className,
className,
attrs.toString(),
className,
className,
className
]);
var _import = set.join(";\r\n");
_import += _import.isEmpty ? "" : ";";
dist = dist.replaceFirst("%t", _import);
dist = dist.replaceFirst("%c", conParams);
//将生成的模板输出
new File("$DIST$name.dart").writeAsStringSync(dist);
}
});
}

String changeFirstChar(String str, [bool upper = true]) {
return (upper ? str[0].toUpperCase() : str[0].toLowerCase()) +
str.substring(1);
}

//将JSON类型转为对应的dart类型
String getType(v, Set<String> set, String current) {
current = current.toLowerCase();
if (v is bool) {
return "bool";
} else if (v is int) {
return "int";
} else if (v is num) {
return "num";
} else if (v is Map) {
return "Map<String,dynamic>";
} else if (v is List) {
return "List";
} else if (v is String) {
//自定义类型的支持
if (v.startsWith("$TAG") && v.endsWith("$TAG")) {
return v.substring(1, v.length - 1);
}
//处理特殊标志
if (v.startsWith("$TAG[]")) {
var className = changeFirstChar(v.substring(3), false);
if (className.toLowerCase() != current) {
set.add('import "$className.dart"');
}
return "List<${changeFirstChar(className)}>";
} else if (v.startsWith(TAG)) {
var fileName = changeFirstChar(v.substring(1), false);
if (fileName.toLowerCase() != current) {
set.add('import "$fileName.dart"');
}
return changeFirstChar(fileName);
}
return "String";
} else {
return "String";
}
}

//替换模板占位符
String format(String fmt, List<Object> params) {
int matchIndex = 0;
String replace(Match m) {
if (matchIndex < params.length) {
switch (m[0]) {
case "%s":
return params[matchIndex++].toString();
}
} else {
throw new Exception("Missing parameter for string format");
}
throw new Exception("Invalid format string: " + m[0].toString());
}

return fmt.replaceAllMapped("%s", replace);
}

void main() {
walk();
}

生成脚本没有变动

1
2
dart ./lib/mo.dart
flutter packages pub run build_runner build --delete-conflicting-outputs

实际结果

普通类型

1
2
3
4
5
6
7
//record.json
{
"content":"拉屎",
"datetime":"$DateTime$",//复杂类型的传入方式
"cost":30,
"useful":true
}

生成结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import 'package:json_annotation/json_annotation.dart';

part 'record.g.dart';
@JsonSerializable()
class Record {
Record({required this.content,required this.datetime,required this.cost,required this.useful});

String content;
DateTime datetime;
int cost;
bool useful;

factory Record.fromJson(Map<String,dynamic> json) => _$RecordFromJson(json);
Map<String, dynamic> toJson() => _$RecordToJson(this);
}

嵌入类型

1
2
3
4
5
6
7
//mock.json
{
"version":1,
"targets":"$[]target",
"records":"$[]record",
"motivations":"$[]motivation"
}

生成结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import 'package:json_annotation/json_annotation.dart';
import "target.dart";
import "record.dart";
import "motivation.dart";
part 'mock.g.dart';
@JsonSerializable()
class Mock {
Mock({required this.version,required this.targets,required this.records,required this.motivations});

int version;
List<Target> targets;
List<Record> records;
List<Motivation> motivations;

factory Mock.fromJson(Map<String,dynamic> json) => _$MockFromJson(json);
Map<String, dynamic> toJson() => _$MockToJson(this);
}