Go 读取 JSON 定义 XLSX 文件生成 Dart Model 类
假设有 JSON 定义的 XLSX 文件,每个工作表定义了不同的 JSON 定义。
那要把这样的 JSON 定义转换成 Model 类(本文是 Dart 语言的 Model 类),该如何处理?
本文代码仓库:github.com/bettersun/j...
JSON 定义工作表1:
其中 userList 下的 user 仅用于描述列表元素,并不是实际存在的 JSON 项目。
JSON 定义工作表2:
处理思路
- 遍历工作表,有 "JSON定义" 关键字的工作表认为有 JSON 定义。
- "JSON定义" 的下一行开始读取 "JSON项目" "类型" "非空" "说明" 所在列。
- 逐行读取JSON项目的定义,保存到列表中。
- 以缩进作为层级关系,组装成 JSON 项目节点树。
- 去掉不必要的节点树。
如工作表1图中 userList 下的 user 仅用于描述列表元素,并不是实际存在的 JSON 项目。 - 找出需要定义为 Model 类的 JSON 项目。
如工作表1图中 的 user 、dept 、 location 和整个 JSON 都需要定义成 Model 类。 - 组装成 Model 类代码,输出到文件。
JSON项目节点
JSON 项目会组装成树,所以一开始 JSON 项目的定义就定义成树节点。
struct 成员:
struct 成员 | 说明 |
---|---|
Level | 层级。读取 XLSX 单元格时通过缩进来计算 |
Name | JSON项目名 |
Comment | JSON项目说明,用于注释 |
ClassName | JSON项目类名 需要定义成类的项目的类名,用于列表的元素对象类的定义。 |
ClassComment | JSON项目类说明 需要定义成类的项目的类说明,用于列表的元素对象类的定义。 |
NodeType | 节点类型 |
NotNull | 不可空 |
Children | 子节点 |
代码:
go
// ItemNode JSON项目节点
type ItemNode struct {
Level int // 层级
Name string // JSON项目名
Comment string // JSON项目说明
ClassName string // JSON项目类名(需要定义成类的项目的类名,用于列表的元素对象类的定义)
ClassComment string // JSON项目类说明(需要定义成类的项目的类说明,用于列表的元素对象类的定义)
NodeType NodeType // 节点类型
NotNull bool // 不可空
Children []*ItemNode // 子节点
}
读取 XLSX 文件
Go 读取 XLSX 文件使用 excelize
。
相关函数
打开文件:
go
f, err := excelize.OpenFile(config.FileName)
获取工作表名列表:
go
f.GetSheetList()
查找工作表名:
go
result, err := f.SearchSheet(v, config.KeywordResponse, true)
分割列名和行号:
go
excelize.SplitCellName(v)
获取所有行的值:
css
rows, err := f.GetRows(sheetName)
最后得到每个JSON项目定义的列表,层级关系用缩进来确认。
各 JSON 项目的节点类型,通过映射根据事先设置的关键字进行处理。
例如:
- 类型是
列表
,则节点类型为TypeArray
- 类型是
字符串
,则节点类型为TypeString
具体读取代码参考仓库。
转换成树
将 JSON 项目的列表转换成树。
- 定义一个根节点。 2循环 JSON 列表。
- 如果 JSON 项目的 Level(等级)是父节点的 Level(等级)+ 1 ,那就将该JSON项目加入到父节点的子节点中。
- 如果当前元素的下一个元素的 Level (等级)大于当前元素的Level (等级),那下一个元素开始所有Level (等级)大于当前元素 Level (等级)的元素,递归调用当前处理。
主要代码:
go
// 将JSON项目定义转换成树
func toTree(node *ItemNode, itemNodeList []*ItemNode) {
nodeList := make([]*ItemNode, 0)
for i := 0; i < len(itemNodeList); i++ {
v := itemNodeList[i]
if v.Level <= node.Level {
break
}
// 当前元素的下一个元素的 level 更大,则下一个元素为当前元素的子
if i < len(itemNodeList)-1 && itemNodeList[i+1].Level > v.Level {
descendant := make([]*ItemNode, 0)
for j := i + 1; j < len(itemNodeList); j++ {
v2 := itemNodeList[j]
if v2.Level > v.Level {
descendant = append(descendant, v2)
}
if v2.Level <= v.Level {
break
}
}
toTree(v, descendant)
}
}
for i := 0; i < len(itemNodeList); i++ {
v := itemNodeList[i]
// 元素的 level 为 参数节点的 leve +1 时,则添加到参数节点的子中
if v.Level == node.Level+1 {
nodeList = append(nodeList, v)
}
if v.Level < node.Level {
break
}
}
node.Children = nodeList
}
得到的 JSON 项目树如下(部分):
去掉 JSON 项目树中实际不存在的节点
如工作表1,其中 userList 下的 user 仅用于描述列表元素,并不是实际存在的 JSON 项目。
这时 userList 的 ClassName 需要设置成 user 的 Name ,然后 ClassComment 需要设置成 user 的 Comment 。
主要处理逻辑:
- 当前节点是列表,且子节点是对象,则将子节点的所有子节点(当前节点的孙节点)放到当前节点的子节点中。
- 递归处理当前节点的所有子节点。
主要代码:
go
// 去掉多余的中间节点
func grandToChild(node *ItemNode) {
for _, child := range node.Children {
// 当前节点是列表,且子节点是对象,则将子节点的所有子节点(当前节点的孙节点)放到当前节点的子节点中。
if node.NodeType == TypeArray && child.NodeType == TypeObject {
node.ClassName = child.Name
node.ClassComment = child.Comment
var children []*ItemNode
for _, grandChild := range child.Children {
grandChild.Level = child.Level
children = append(children, grandChild)
}
node.Children = children
}
grandToChild(child)
}
}
得到的树如下(部分):
可以看到 user 节点已经不存在了,userList 的 ClassName 和 ClassComment 也变成了 原来 user 的 Name 和 Comment 。
整理出所有需要定义成类的 JSON 项目
由最初的定义可以得到,需要定义成类的 JSON 项目有下面几个。
- 整个 JSON 定义
- user 用户信息
- dept 部门
- location 位置
也就是根节点和所有节点类型是 数组 或 对象的 JSON 项目。
方法用了一个 JSON 项目,把所有需要定义成类的 JSON 项目添加到了该节点的子节点中。
go
// 所有需要定义成类的对象
func allObject(node *ItemNode, objectNode *ItemNode) {
objectNode.Children = append(objectNode.Children, node)
for _, v := range node.Children {
// 列表或对象时递归调用
if v.NodeType == TypeArray {
allObject(v, objectNode)
}
if v.NodeType == TypeObject {
allObject(v, objectNode)
}
}
}
得到4个需要定义成类的 JSON 项目:
转换成 Dart Model 类
Dart Model 类定义需要的内容都有了,接下来就是组装字符串了,具体可参考代码,不再赘述。
本例代码中,
生成的根节点类名暂时使用了 root + 工作表下标,如 root0,root1。
生成的各 Model 类是添加了 json_serializable
的类。
需要使用相关命令再生成 xxx.g.dart
文件,即可消除编译错误。
文件名使用了 JSON 项目的 Name 或 ClassName 的蛇形(下划线连接小写字母)。
注: 输出到文件时,不存在的目录需要先创建。
生成的目录及文件:
root0.dart
dart
import 'package:json_annotation/json_annotation.dart';
import 'model.dart';
part 'root0.g.dart';
///
@JsonSerializable()
class Root0{
/// 用户信息列表
final List<User> userList;
/// 部门
final Dept dept;
/// 位置
final Location? location;
Root0({
required this.userList,
required this.dept,
this.location,
});
factory Root0.fromJson(Map<String, dynamic> json) => _$Root0FromJson(json);
Map<String, dynamic> toJson() => _$Root0ToJson(this);
}
user.dart
dart
import 'package:json_annotation/json_annotation.dart';
import 'model.dart';
part 'user.g.dart';
/// 用户信息
@JsonSerializable()
class User{
/// 用户ID
final int userId;
/// 用户名
final String name;
/// 状态
final bool status;
/// 性别
final int? sex;
/// 年龄
final int? age;
/// 地址
final String? address;
User({
required this.userId,
required this.name,
required this.status,
this.sex,
this.age,
this.address,
});
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
Map<String, dynamic> toJson() => _$UserToJson(this);
}
结束语
难点就在将 JSON 项目列表转换成树的处理。
一开始各种乱试,总是不成功。
后来又用笔纸理了一下逻辑,才算弄清楚。
哎, 工作这么多年,连个树都写不好,着实有些丧了。