• 首页
  • 感性生活,理性思考。


Flutter自动把API JSON对象生成Model代码

常在Flutter里混,怎能不看看这本书Flutter实战

不想理这些步骤,想一步到位用工具,请直接拉到最后。

项目地址

初始化一个项目

flutter create my_app

添加依赖

pubspec.yaml

dependencies:
  # Your other regular dependencies here
  json_annotation: ^2.0.0

dev_dependencies:
  # Your other dev_dependencies here
  build_runner: ^1.0.0
  json_serializable: ^2.0.0

新增目录或文件

/json
/lib/models
/mo.dart
/mo.sh
/template.dart

模拟一个API的json

{
  "name": "John Smith",
  "email": "john@example.com",
  "mother":{
    "name": "Alice",
    "email":"alice@example.com"
  },
  "friends":[
    {
      "name": "Jack",
      "email":"Jack@example.com"
    },
    {
      "name": "Nancy",
      "email":"Nancy@example.com"
    }
  ]
}

自动化脚本

用dart实现了一个脚本,它可以自动生成模板,并直接将JSON转为Model类,下面我们看看怎么做:

  • 定义一个"模板的模板",名为"template.dart":
import 'package:json_annotation/json_annotation.dart';
%t
part '%s.g.dart';
@JsonSerializable()
class %s {
    %s();

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

模板中的“%t”、“%s”为占位符,将在脚本运行时动态被替换为合适的导入头和类名。

  • 写一个自动生成模板的脚本(mo.dart)

它可以根据指定的JSON目录,遍历生成模板,在生成时我们定义一些规则:

脚本我们通过Dart来写,源码如下:

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.dart").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();
      (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("    ");
      });
      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 );
      //将生成的模板输出
      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 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[]")){
      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();
}

写一个Shell脚本(mo.sh)

dart mo.dart
flutter packages pub run build_runner build --delete-conflicting-outputs

至此,我们的脚本写好了,我们在根目录下新建一个json目录,然后把user.json移进去,然后在lib目录下创建一个models目录,用于保存最终生成的Model类。现在我们只需要一句命令即可生成Model类了:
本目录下

mo.sh  

运行后,一切都将自动执行,现在会在lib/models里新增两个文件 person.dart person.g.dart

变形

我们发现这样的Model不是很规范,也就是说不够清晰,每个Person都有name 、email 、 mother和friends四个字段,由于mother也是一个Person,朋友是多个Person(数组),:
修改person.json

{
  "name": "John Smith",
  "email": "john@example.com",
  "mother":"$person",
  "friends":"$[]person"
}

我们使用美元符“$”作为特殊标志符(如果与内容冲突,可以修改mo.dart中的TAG常量,自定义标志符),脚本在遇到特殊标志符后会先把相应字段转为相应的对象或对象数组,对象数组需要在标志符后面添加数组符“[]”,符号后面接具体的类型名,此例中是person。

同理,加入我们给User添加一个Person类型的 boss字段:

{
  "name": "John Smith",
  "email": "john@example.com",
  "boss":"$person"
}

重新运行mo.sh,生成的user.dart如下:

import 'package:json_annotation/json_annotation.dart';
import "person.dart";
part 'user.g.dart';

@JsonSerializable()

class User {
    User();

    String name;
    String email;
    Person boss;
    
    factory User.fromJson(Map<String,dynamic> json) => _$UserFromJson(json);
    Map<String, dynamic> toJson() => _$UserToJson(this);
}

可以看到,boss字段已自动添加,并自动导入了“person.dart”。

更多详情可以查看电子书。

草包:Json_model 包

我们发现,这样会导致目录代码挺乱的,最后来个大杀器,直接引用大牛的依赖就OK了

如果每个项目都要构建一个上面这样的脚本显然很麻烦,为此,我们将上面脚本和生成模板封装了一个包,已经发布到了Pub上,包名为Json_model (opens new window),开发者把该包加入开发依赖后,便可以用一条命令,根据Json文件生成Dart类。另外Json_model (opens new window)处于迭代中,功能会逐渐完善,所以建议读者直接使用该包(而不是手动复制上面的代码)。

前往pub.dev查看 点击这里

这个库包已年久失修了!!!!会有Value not in range: -1阴魂。

大杀器:json_to_model包

  • 引入 json_to_model: ^2.2.0
  • 新增jsons文件夹
  • 添加json对象文件
  • flutter pub run json_to_model
  • done

本文链接:

https://chao.asia/tech/149.html

1 + 8 =
快来做第一个评论的人吧~
lamu.png