Flutter json解析json_serializable的使用及自动化生成模板

一、json_serializable使用步骤

1.集成json_serializable

pubspec.yaml 添加以下依赖

dependencies:
  json_annotation: ^2.0.0

dev_dependencies:
  build_runner: ^1.0.0
  json_serializable: ^2.0.0

添加完记得执行 flutter packages get

2.创建Model

把你的json数据到这里(点我)生成一下model文件
如下:

WechatIMG5.jpeg

  1. 填入json数据
  2. 填写类名
  3. 复制或下载

3.生成文件

在项目根目录下执行flutter packages pub run build_runner watch即可生成xxx.g.dart

4.解析及序列化

注意导包import 'dart:convert';

///json转model
String jsonString = '{"name": "Tony","email": "tony@example.com"}'
Map userMap = json.decode(jsonString);
var user = User.fromJson(userMap);
///model转json
String jsonEncode = json.encode(user);
print(jsonEncode);

二、自动化生成模板

上述过程需要每次把json去生成网站去转化成Model,接下来我们直接在本地生成,只需要写个user.json文件再执行下命令即可。这样每次json结构有修改后可以直接修改json文件再执行下命令即可,并且json结构能存在本地方便查看。

1.使用json_model

集成json_model
dev_dependencies: 
  json_model: ^0.0.2 #最新版本
使用
  1. 在工程根目录下创建一个名为 "jsons" 的目录;
  2. 创建或拷贝Json文件到"jsons" 目录中 ;
  3. 运行 pub run json_model (Dart VM工程)or flutter packages pub run json_model(Flutter中) 命令生成Dart model类,生成的文件默认在"lib/models"目录下

具体文档地址:https://github.com/flutterchina/json_model

2.一点点小优化

上述方式直接导入插件包已经很方便了,但使用过程中遇到了一点问题:

  • 生成的model文件名和json文件名一样,如果文件名有下划线_时生成的类名也是有下划线的,但我习惯使用驼峰命名
  • 同上,当字段名中有下划线_,生成的字段也是有下划线的,要想使用驼峰命名需要手动在字段上方加上@JsonKey(name: 'user_name')
  • 自动解析数据类型只支持bool num Map List等几种常见类型,如果是DateTime类型或其他类型的字段只能解析成String。
  • 生成的model没有完美的格式化,有强迫症的还得再手动格式化一下

如果你感觉这样不友好或有自己的书写习惯那么就自己动手吧。我这里按照自己的习惯改了一下:

  • 类名,字段名驼峰命名
  • 支持DateTime类型(后期有其他支持可以添加)
  • 完美的格式化

做法:不使用json_model,自己动手

  1. 在工程根目录下创建一个名为 "jsons" 的目录;
  2. 创建或拷贝Json文件到"jsons" 目录中 ;
  3. 在项目根目录下创建 mo.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 = "import 'package:json_annotation/json_annotation.dart';\r\n%t\npart '%s.g.dart';\r\n\r\n@JsonSerializable()\r\nclass %s {\r\n  %s();\r\n\r\n  %sfactory %s.fromJson(Map<String, dynamic> json) => _\$%sFromJson(json);\r\n\r\n  Map<String, dynamic> toJson() => _\$%sToJson(this);\r\n}\r\n";
  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;
        /// #############################
        ///处理key包含"_"时,转为驼峰并加上@JsonKey(name="key")
        if (key.contains("_")) {
          attrs.write('@JsonKey(name: "$key")');
          attrs.write("\r\n  ");
          attrs.write(getType(v, set, name));
          attrs.write(" ");
          attrs.write(changeToCamelCase(key, false));
          attrs.writeln(";");
          attrs.write("\r\n  ");
        } else {
          attrs.write(getType(v, set, name));
          attrs.write(" ");
          attrs.write(key);
          attrs.writeln(";");
          attrs.write("\r\n  ");
        }
      });
      String className = "";
      /// #############################
      ///处理有"_"时class不是驼峰命名
      if (name.contains("_")) {
        className = changeToCamelCase(name, true);
      } else {
        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);
    }
  });
}
/// #############################
///转为驼峰命名
///big 是否大驼峰
String changeToCamelCase(String word, bool big) {
  if (word.contains("_")) {
    String result = "";
    List<String> words = word.split("_");
    for (var value in words) {
      result += (value[0].toUpperCase() + value.substring(1).toLowerCase());
    }
    return big ? result : (result[0].toLowerCase() + result.substring(1));
  } else {
    return big
        ? word[0].toUpperCase() + word.substring(1)
        : word[0].toLowerCase() + word.substring(1);
  }
}

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) {
    /// #############################
    ///添加DateTime类型
    try {
      DateTime dateTime = DateTime.parse(v);
      if (dateTime != null) {
        return "DateTime";
      }
    } catch (e) {}

    //处理特殊标志
    if (v.startsWith("$TAG[]")) {
      var className = changeFirstChar(v.substring(3), false);
      if (className.toLowerCase() != current) {
        set.add('import "$className.dart"');
      }
      /// #############################
      /// 自定义model类型名字大驼峰命名
      return "List<${changeToCamelCase(className, true)}>";
    } else if (v.startsWith(TAG)) {
      var fileName = changeFirstChar(v.substring(1), false);
      if (fileName.toLowerCase() != current) {
        set.add('import "$fileName.dart"');
      }
      /// #############################
      /// 自定义model类型名字大驼峰命名
      return changeToCamelCase(fileName, true);
    }
    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();
}

这里是生成model的方法及模板,Json_model的前身,来自这里
其中注释包含 “#############################”的地方是修改过的地方,大家要有自己的需求可以自行修改。

  1. 在项目根目录下创建mo.sh文件,内容如下:
#!/usr/bin/env bash
dart mo.dart
flutter packages pub run build_runner build --delete-conflicting-outputs

这个是脚本,把命令打包后一起执行

注:如果你没有配置dart环境变量,需要配置一下:(Mac下)
  • vim ~/.bash_profile
  • 添加export PATH=your flutter path/flutter/bin/cache/dart-sdk/bin:$PATH
  • source ~/.bash_profile
  • dart --version正常显示版本号即可
  1. 以上都配置完后只需一步,项目根目录下执行sh mo.sh,ok,一切完美的搞定了,给大家看看结果
    WechatIMG7.jpeg
WechatIMG8.jpeg
WechatIMG9.jpeg

重要参考:
https://www.jianshu.com/p/4210536124b1
https://book.flutterchina.club/chapter10/json_model.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349