Dart 读取 json 内容生成 Model 类

Dart 读取 json 文件生成 Model 类

Flutter 开发者会了解到 JSON to Dart (javiercbk.github.io) 这样的网站, 可以把 json 文件内容贴进去,然后直接生成 Dart 的 Model 类。

不过没有太多定制选项,所以想自己开发一个同样的工具。

思路:

  1. 使用 dart 的 jsonDecode() 方法将 json 转化成Dart对象(Map 或 List) 。
  2. 递归解析Dart对象组装成 树。
  3. 遍历 树 找出需要定义为 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 解析后的数据
  • 一个根节点
  1. 如果数据为 List 类型,则节点类型就是 list。

    1. 然后取第一个元素
    2. 如果第一个元素是Map,则递归调用当然方法。
    3. 如果第一个元素是List,则定义一个子节点,再以第一个元素的数据和子节点,递归调用当前方法。
      子节点类型为列表(枚举中的 list) ,递归调用完,将该子节点添加到参数节点的子节点中。
    4. 如果是其它类型(字符串、数字、true/false、null),也定义一个子节点,设置类型,并将该子节点添加到参数节点的子节点中。
  2. 如果数据类型不是 List ,则认为是 Map ,即对象(枚举中的 object) 。

    1. 遍历 Map 的键,并根据值判断 json 项目的类型。
      1-1. 如果Map 项目的值是 List 或 Map,再以项目的值和子节点,递归调用当前方法。
      1-2. 各个键值对应定义一个子节点,将各子节点添加到参数节点的子节点中。

主要代码

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类的属性都是可空的,可根据需要改为不可空的。

总结

  1. 最初是考虑用Dart一个字符一个字条地读取 json 字符串解析json。
    后来通过阅读网上文章(鸣谢)的代码,发现可以使用 Dart 的 jsonDecode 方法将 json 字符串转换成 Dart 对象, 然后再遍历 Dart 对象即可。
  2. 递归的调用时机是自己试了很长时间才做到的。
    也是一开始没有方向地瞎写,没找到正确的方法,还是需要先理清思路再去写代码。
  3. 该工具现在主要是生成 Dart 类,也可以添加更多的定制选项生成其它多种语言的 Model 类。

鸣谢:Flutter真香,我用它写了个桌面版JSON解析工具 - 掘金 (juejin.cn)


相关推荐
AiFlutter15 小时前
Flutter之Package教程
flutter
Mingyueyixi19 小时前
Flutter Spacer引发的The ParentDataWidget Expanded(flex: 1) 惨案
前端·flutter
crasowas1 天前
Flutter问题记录 - 适配Xcode 16和iOS 18
flutter·ios·xcode
老田低代码2 天前
Dart自从引入null check后写Flutter App总有一种难受的感觉
前端·flutter
AiFlutter3 天前
Flutter Web首次加载时添加动画
前端·flutter
ZemanZhang4 天前
Flutter启动无法运行热重载
flutter
AiFlutter4 天前
Flutter-底部选择弹窗(showModalBottomSheet)
flutter
帅次5 天前
Android Studio:驱动高效开发的全方位智能平台
android·ide·flutter·kotlin·gradle·android studio·android jetpack
程序者王大川5 天前
【前端】Flutter vs uni-app:性能对比分析
前端·flutter·uni-app·安卓·全栈·性能分析·原生
yang2952423615 天前
使用 Vue.js 将数据对象的值放入另一个数据对象中
前端·vue.js·flutter