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

天玉發表於2019-06-18

一、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"目錄下

具體文件地址:github.com/flutterchin…

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

重要參考:
www.jianshu.com/p/421053612…

book.flutterchina.club/chapter10/j…

相關文章