Dart 读取 json 文件生成 Model 类
Flutter 开发者会了解到 JSON to Dart (javiercbk.github.io) 这样的网站, 可以把 json 文件内容贴进去,然后直接生成 Dart 的 Model 类。
不过没有太多定制选项,所以想自己开发一个同样的工具。
思路:
- 使用 dart 的 jsonDecode() 方法将 json 转化成Dart对象(Map 或 List) 。
- 递归解析Dart对象组装成 树。
- 遍历 树 找出需要定义为 Model 类的对象节点,分别转换成 Model 类。
解析 json 文件获取 json 结构及项目类型
节点类
这里的节点类是指 json 项目的节点,不是 json 树的键值的节点,二者是不同的概念。
json
{
"hello": {
"happy": 0,
"ok" : "yes"
}
}
节点类的定义包含以下成员属性:
-
项目名
-
节点类型(枚举类型)
-
阶层 便于确认父子关系
-
同阶层项目下标
-
只有值(值的数组的情况)
节点类型为数组时需要判断各元素是否是基本类型,还是json对象。
-
子节点
代码如下:
dart
/// json 节点
class Node {
/// 项目名
String name;
/// 节点类型
NodeType type = NodeType.nil;
/// 阶层
int level;
/// 下标
int index;
/// 只有值(值的数组的情况)
bool onlyValue;
/// 子节点
List<Node> children;
Node({
this.type = NodeType.nil,
this.level = 0,
this.index = 0,
this.name = '',
this.onlyValue = false,
this.children = const [],
});
}
节点类型枚举类:
dart
enum NodeType {
bool,
num,
int,
double,
string,
list,
object,
/// null
nil,
/// 默认
none,
}
解析逻辑
方法参数:
- jsonDecode 解析后的数据
- 一个根节点
-
如果数据为 List 类型,则节点类型就是 list。
- 然后取第一个元素
- 如果第一个元素是Map,则递归调用当然方法。
- 如果第一个元素是List,则定义一个子节点,再以第一个元素的数据和子节点,递归调用当前方法。
子节点类型为列表(枚举中的 list) ,递归调用完,将该子节点添加到参数节点的子节点中。 - 如果是其它类型(字符串、数字、true/false、null),也定义一个子节点,设置类型,并将该子节点添加到参数节点的子节点中。
-
如果数据类型不是 List ,则认为是 Map ,即对象(枚举中的 object) 。
- 遍历 Map 的键,并根据值判断 json 项目的类型。
1-1. 如果Map 项目的值是 List 或 Map,再以项目的值和子节点,递归调用当前方法。
1-2. 各个键值对应定义一个子节点,将各子节点添加到参数节点的子节点中。
- 遍历 Map 的键,并根据值判断 json 项目的类型。
主要代码
json 解析:
dart
import '../model/model.dart';
import 'type_util.dart';
class JsonAnalyzer {
/// 遍历 json ,得到 json 项目树
static void travel(dynamic data, Node node) {
if (data is List) {
node.type = NodeType.list;
var first = data.firstOrNull;
if (first != null) {
if (first is Map) {
travel(first, node);
return;
}
if (first is List) {
List<Node> children = [];
int level = node.level + 1;
Node child = Node(
level: level,
name: 'inner_list_$level',
index: 0,
);
child.type = NodeType.list;
travel(first, child);
children.add(child);
node.children = children;
return;
}
// 值的数组
node.onlyValue = true;
List<Node> children = [];
Node child = Node(
level: node.level + 1,
name: '--none key--',
index: 0,
);
child.type = TypeUtil.nodeTypeOfValue(first);
children.add(child);
node.children = children;
}
} else {
if (node.type == NodeType.none) {
node.type = NodeType.object;
}
List<Node> children = [];
children.addAll(node.children);
int index = 0;
for (MapEntry item in data.entries) {
Node child = Node(
level: node.level + 1,
name: item.key,
index: index,
);
index++;
child.type = TypeUtil.nodeTypeOfValue(item.value);
if (item.value is List || item.value is Map) {
travel(item.value, child);
}
children.add(child);
}
node.children = children;
}
}
}
TypeUtil.nodeTypeOfValue(item.value) 为类型判断方法,使用 Dart 语言的 is Type 来判断。
例
if (value is String) { nodeType = NodeType.string; }
以上是解析 json 的处理,得到的是一个包含 json 项目定义的树。
遍历出需要定义 Dart 类的节点
根节点视为要定义成 Dart 类的节点。 然后遍历 json 的项目类型节点树,如果节点的节点类型是 Map 或者 是纯值(即各元素是基本类型,不是对象)的 List ,则视为要定义成 Dart 类的节点,并继续递归遍历。
这样就能把所有需要定义成 Dart 类的节点找出来。
代码:
dart
/// json对象中所有的类
static void allClass(Node node, List<Node> classList) {
classList.add(node);
for (var item in node.children) {
if (item.type == NodeType.list && !item.onlyValue) {
allClass(item, classList);
}
if (item.type == NodeType.object) {
allClass(item, classList);
}
}
}
生成 Dart 类代码
上面已经整理出需要定义成 Dart 类的节点,再根据每个节点生成 Dart类代码即可。
最初解析 json 的处理中,如果最顶层是数组,会少一层数组的定义。
这种情况需要再添加一个最外层类的定义。
dart
List<Node> modifyClassList(List<Node> classList) {
List<Node> modifiedClassList = [];
for (var item in classList) {
if (item.type == NodeType.list && item.level == 0) {
// 顶层为数组的情况下,添加一个父类名
Node parentNode = Node(
type: NodeType.object,
level: -1,
name: 'parent',
children: [
Node(
type: NodeType.list,
name: item.name,
),
],
);
modifiedClassList.add(parentNode);
}
}
modifiedClassList.addAll(classList);
return modifiedClassList;
}
接下来就可以愉快地生成 Dart 代码了。
生成类定义代码
dart
/// 生成类定义代码
String toClass(List<Node> classList) {
List<String> classDefineList = [];
for (var item in classList) {
// 类名
String classNm = className(item);
// 属性定义
List<String> properties = [];
for (var prop in item.children) {
properties.add(propertyDefine(prop));
}
String props = properties.join('\n');
props = '\n$props\n';
// 构造函数
String sClass = constructor(item);
// 类的所有代码
String classDef = "class $classNm{\n$props\n$sClass\n}\n";
classDefineList.add(classDef);
}
return classDefineList.join('\n');
}
Dart 类的成员属性:
ini
String propertyDefine(Node node) {
String property = '';
String type = propertyType(node);
String name = node.name;
if (node.type == NodeType.list) {
property = 'List<$type>? $name';
} else if (type == CodeConst.defaultType) {
property = '$type $name';
} else {
property = '$type? $name';
}
if (_option.isPropertyFinal) {
property = 'final $property';
}
return '$property;';
}
Dart 类的成员属性类型:
节点类型为 list 时,需要看一下是否是只有基本类型的数组。
如果是只有基本类型的数组,则需要再去判断第一个元素的类型。
ini
/// 属性类型
String propertyType(Node node) {
String type = CodeConst.defaultType;
if (node.type == NodeType.list) {
if (node.onlyValue) {
type = TypeUtil.typeOfValue(node.children[0]);
} else {
type = className(node);
}
}
if (node.type == NodeType.object) {
type = className(node);
}
if (type == CodeConst.defaultType) {
type = TypeUtil.typeOfValue(node);
}
return type;
}
Dart类名:
这里只是将节点名称的首字母大写。
dart
String className(Node node) {
String name = CharUtil.capitalizeHead(node.name);
return name;
}
构造函数:
将各属性加到构造函数中。
dart
String constructor(Node node) {
List<String> properties = [];
for (var item in node.children) {
String name = item.name;
properties.add('this.$name');
}
properties.add('');
String propertiesCon = properties.join(',\n');
String classNm = className(node);
return '$classNm({\n$propertiesCon});\n';
}
这样就能生成 json 各阶层对象对应的 Dart 类了。
现在生成的Dart类的属性都是可空的,可根据需要改为不可空的。
总结
- 最初是考虑用Dart一个字符一个字条地读取 json 字符串解析json。
后来通过阅读网上文章(鸣谢)的代码,发现可以使用 Dart 的 jsonDecode 方法将 json 字符串转换成 Dart 对象, 然后再遍历 Dart 对象即可。 - 递归的调用时机是自己试了很长时间才做到的。
也是一开始没有方向地瞎写,没找到正确的方法,还是需要先理清思路再去写代码。 - 该工具现在主要是生成 Dart 类,也可以添加更多的定制选项生成其它多种语言的 Model 类。
鸣谢:Flutter真香,我用它写了个桌面版JSON解析工具 - 掘金 (juejin.cn)