在Flutter開發過程當中快速生成json解析模板類的工具 | 掘金技術徵文

若是你已經入門了Flutter的開發,並準備實現一個包含網絡數據請求並解析數據顯示在界面上的應用,那麼你必定須要瞭解在Flutter中應該如何完成對JSON數據的解析和處理。

若是尚未看過官方的相關文檔,請移步至JSON和序列化查看。java

能夠看到Flutter使用的sdk中提供了dart:convert 包用於JSON的序列化和反序列化,其功能與iOS的NSJSONSerialization相似,都是直接將JSON數據轉成NSDictionary / NSArray(或者JAVA中的Map/List), 而後開發時經過輸入字段名字符串的方式從中取值,很是的不方便。

另外一種方式則是編寫模型類,這樣能夠在已知JSON結構和字段的狀況下預先設置好類結構,開發時則能夠方便地經過'點語法'進行數據字段取值,得到IDE代碼提示的輔助,從而減小手誤提升開發效率。然而編寫大量類似的模板類代碼無聊又費力,即便使用官方提供的json_serializable package包進行代碼生成也仍是至關麻煩。

若是開發過android、iOS或者java後臺應用,那麼你應該曾使用過各類插件用於將JSON字符串解析生成模板類(例如GsonFormatESJsonFormat ),這些工具極大提升了開發效率。python

那麼Flutter開發中有沒有相似的工具可使用呢?本文就將介紹我參照這些工具專爲Flutter編寫的一個小工具(還有一個相似的在線工具:JSON to Dart,感興趣的能夠試試~~):

JSONFormat4Flutter

github地址:github.com/debuggerx01…

使用操做演示:

使用說明

1.界面操做 (參考錄屏:parse.gif)

  1. 工具運行之後,先將複製好的json字符串粘貼到左側文本框,而後點擊'格式化'按鈕;若是提示出錯請檢查json是否合法
  2. 格式化成功後左側json將會按照縮進格式化顯示,而且右側表格將顯示分析得出的json結構,'Fields'列顯示層級和原始分析數據,'Name'列顯示每一個字段的名稱,'Type'列用於設定字段的數據類型
    1. 對於普通數據類型(int、 double、 boolean、 String),Types列的類型將會自動給出,請儘可能避免在上面滾動鼠標滾輪致使類型選擇改變
    2. 對於值null的字段,Types列的類型會自動設置爲Object,並以黃色背景做爲警告。此時若是直接生成代碼也是可使用的,只是該字段在使用時可能須要手動強轉,因此建議在知曉該字段實際類型狀況下儘可能補全json字符串後再點擊'格式化',或者在類型下拉框中指定實際的基本數據類型
    3. 對於自定義對象類型(或者說字典/Map),'Fields'的對應輸入框將留空並設爲紅色背景,須要您手動輸入類型名稱,並請注意:
      1. 任意一個字段沒有輸入類型名時點擊代碼生成按鈕,都將彈出警告提示並拒絕生成代碼
      2. 設置類型名時能夠參考同一行'Name'欄的值進行設置以方便使用時識別字段,通常狀況下推薦直接將'Name'欄內容首字母大寫做爲類型名
      3. 可是須要注意,類型名不可與'Name'欄內容徹底相同,且不能是dart中的關鍵字,不然生成的代碼將包含語法錯誤
      4. 通常狀況下第一行的數據類型爲對象且'Name'欄內容爲空,設置第一列的'Types'即爲生成的bean的頂級對象類名,推薦使用'該json的做用+Resp/Bean'形式進行命名以方便管理
    4. 對於數組類型,'Types'欄將被自動設置,而且:
      1. 數組的泛型類型取決於數組的內容的類型,也就是下一行設置的類型;當數組下一行的內容類型變化時泛型也會自動改變
      2. 支持數組的嵌套泛型傳遞
      3. 支持空數組,而且生成的代碼中其泛型會被設置爲dynamic
    5. 特殊的,若是json自己的頂層級不是對象而是數組,那麼須要爲第一行的'Name'欄設置類型名稱,獲取頂層級數組數據的方式爲對象bean.list
  3. 確認設置無誤後,點擊'生成Bean'按鈕,左側json顯示欄的內容將被替換爲生成的代碼,可使用鼠標鍵盤全選複製,或者直接點擊下方的'複製'按鈕,而後將代碼粘貼到IDE中,完成解析流程

2.生成代碼說明 (參考錄屏:use.gif)

  1. 反序列化(json字符串->對象)
    將生成的代碼粘貼到dart源文件中後,便可以在任意地方導包使用,通常方法爲(以http.get請求爲例):linux

    var response = await HTTP.get(url);
    var resp = BeanResp(response.body);
    複製代碼

    也就是說,將請求到的json內容做爲參數傳遞給BeanResp的默認構造函數,這樣生成的resp對象便是請求到內容的實體。 須要說明的是,默認構造既能夠傳入json的原始字符串,也能夠傳入已經用原生json.decode()方法解析過的json對象(這主要是爲了照顧使用dio庫進行數據請求時結果數據會被自動解析成json對象的狀況)。 只有頂級對象擁有默認構造方法,而其餘子層級對象將使用xxx.fromJson()的命名構造進行對象建立。android

  2. 序列化(對象->json字符串)
    與官方樣例的處理方式不一樣,直接調用對象的toString()方法便可獲得json字符串完成序列化操做git

  3. 手動建立對象
    爲了方便大部分使用場景下的便利性,bean的默認構造函數被用來實現反序列化,因此若是想要在代碼中手動傳參建立bean對象,可使用xxx.fromParams()命名構造來完成。github

簡易運行方式:

Release 頁面中,選擇下載對應平臺最新的二進制文件後——json

linux:

在程序目錄打開終端後執行:chmod u+x Formatter_linux && ./Formatter_linuxwindows

mac:

在程序目錄打開終端後執行:chmod u+x Formatter_mac && ./Formatter_mac數組

windows:

直接雙擊運行 Formatter_win.exebash

源碼運行(以MAC爲例)

沒有python運行環境的用戶須要先安裝python

mac中可使用以下命令安裝

brew install python3
brew install pip3
複製代碼

pip3是python3的包管理工具

brew 能夠參考下面的連接

brew.sh/index_zh-cn

運行庫的時候會可能會提示

Traceback (most recent call last):
  File "formater.py", line 8, in <module>
    from mainwindow import *
  File "/Users/cjl/IdeaProjects/flutter/sxw-flutter-app/JSONFormat4Flutter/mainwindow.py", line 9, in <module>
    from PyQt5 import QtCore, QtGui, QtWidgets
ModuleNotFoundError: No module named 'PyQt5'

複製代碼

這時候能夠直接用 pip3 install PyQt5 pip3 install pyperclip 等待安裝完成

(注:brew安裝最新版python3可能會出現ssl模塊丟失致使pip3沒法正常使用,此時也能夠考慮直接在python官網下載pkg包方式安裝python)

後面使用就是在命令行敲入 python3 formatter.py

一些問題的說明

若是有什麼問題,請在github上與我聯繫。下面列出幾個已有的問題:

  1. 爲何不作成AS/idea插件,而選擇PyQt編寫工具

    緣由其一是我曾寫過簡單的AS/idea插件,發現開發資料至關匱乏,調試也很麻煩,雖然也有一些現成的插件源碼可供參考,可是開發這樣一個工具的週期預計會大幅超過我當時容許的時間(本工具第一個可用版本的實際開發時間爲3天多一點);
    其二是因爲Flutter既能夠用AS/idea開發也能夠用VSCode開發的特性(並且據我觀察二者使用人數比例相仿),兩種IDE體系下開發插件的語言工具及流程都徹底不一樣,一旦我選擇了開發原生IDE插件,那麼勢必須要編寫維護兩份代碼,成本過高;
    其三是由於python強大的文本處理能力使得它特別適合這種模板生成的工做,並且正巧以前的工做中就有利用python解析excel數據生成其餘語言源代碼的腳本,能夠快速借用已有的代碼邏輯來實現功能;
    其四是得益於PyQt強大的跨平臺能力,能夠方便快捷地打出三個桌面平臺的應用包,從而達到接近原生插件的通用性(期待Flutter的桌面版項目早日成熟,這樣之後開發桌面工具也多一種選擇)
    另外,能夠參考issue #9中的演示,將工具添加到IDE的外部工具中以方便打開,這樣使用體驗就與IDE插件更加類似了

  2. 生成的代碼與官網上的樣例風格不符

    由於我寫這個玩意的時候官網文檔尚未如今的樣例模板(也可能單純是我沒注意到),因此徹底是本身構思的方式來作的。後來看到樣例後也想過仿照那個格式改一下,但轉念一想其實也沒什麼本質區別,加上又忙就一直沒動。我作這個工具主要仍是以可以處理各類奇怪json爲目標的,好比爲了處理多層list嵌套,頂級結構爲list,list內部元素爲基本數據類型等,這些花了很多精力……並且生成的代碼裏沒用樣例裏類型轉換,因此dart2升級強類型模式之後不少人根據那個樣板寫的json解析都報錯的,而這個工具生成的卻一直能用,既然可以保證功能,具體的風格方式問題應該也不必太過糾結了

  3. 如何實現經過泛型返回不一樣的類型

    這主要是一些以前作android開發的同窗習慣於android開發中流行的’網絡請求封裝‘,就是假設後臺返回的JSON擁有相同的一級結構,好比都是
    {"code":0,"msg":"success","data":{}}
    這種結構,全部的請求結果都須要先進行code和msg字段的判斷處理,而後要使用的實際數據全在data字段中,因此但願有一個統一的處理函數能夠進行code和msg字段的處理,並經過指定泛型或class來返回指定類型的data內容對象。
    雖然我我的認爲這只是個習慣問題,不必堅持,因爲dart語法特性確實很差實現,那麼不如換種形式同樣能處理地很好,可是由於見到不止一我的有這種疑問,因此我試着用這種思路寫了個dart版本的demo僅供參考,但願有人能提供更好更優雅的方案:

Person接口返回的json: {"code":0,"msg":"success","data":{"name":"debuggerx","age":26}} Phone接口返回json: {"code":0,"msg":"success","data":{"model":"MI6X","price":1999}}

PersonResp.dart:

import 'dart:convert' show json;
class PersonResp {
  int code;
  String msg;
  Person data;

  PersonResp.fromParams({this.code, this.msg, this.data});

  factory PersonResp(jsonStr) => jsonStr is String ? PersonResp.fromJson(json.decode(jsonStr)) : PersonResp.fromJson(jsonStr);
  
  PersonResp.fromJson(jsonRes) {
    code = jsonRes['code'];
    msg = jsonRes['msg'];
    data = new Person.fromJson(jsonRes['data']);
  }

  @override
  String toString() {
    return '{"code": $code,"msg": ${msg != null?'${json.encode(msg)}':'null'},"data": $data}';
  }
}

class Person {
  int age;
  String name;
  
  Person.fromParams({this.age, this.name});
  
  Person.fromJson(jsonRes) {
    age = jsonRes['age'];
    name = jsonRes['name'];
  }

  @override
  String toString() {
    return '{"age": $age,"name": ${name != null?'${json.encode(name)}':'null'}}';
  }
}
複製代碼

PhoneResp.dart:

import 'dart:convert' show json;
class PhoneResp {
  int code;
  String msg;
  Phone data;

  PhoneResp.fromParams({this.code, this.msg, this.data});

  factory PhoneResp(jsonStr) => jsonStr is String ? PhoneResp.fromJson(json.decode(jsonStr)) : PhoneResp.fromJson(jsonStr);
  
  PhoneResp.fromJson(jsonRes) {
    code = jsonRes['code'];
    msg = jsonRes['msg'];
    data = new Phone.fromJson(jsonRes['data']);
  }

  @override
  String toString() {
    return '{"code": $code,"msg": ${msg != null?'${json.encode(msg)}':'null'},"data": $data}';
  }
}

class Phone {
  int price;
  String model;

  Phone.fromParams({this.price, this.model});
  
  Phone.fromJson(jsonRes) {
    price = jsonRes['price'];
    model = jsonRes['model'];
  }

  @override
  String toString() {
    return '{"price": $price,"model": ${model != null?'${json.encode(model)}':'null'}}';
  }
}
複製代碼

只須要編寫一個BaseResp.dart以下:

import 'dart:convert' show json;

class BaseResp<T> {
  int code;
  String msg;
  T data;

  factory BaseResp(jsonStr, Function buildFun) =>
      jsonStr is String ? BaseResp.fromJson(json.decode(jsonStr), buildFun) : BaseResp.fromJson(jsonStr, buildFun);

  BaseResp.fromJson(jsonRes, Function buildFun) {
    code = jsonRes['code'];
    msg = jsonRes['msg'];
    /// 這裏能夠作code和msg的處理邏輯
    data = buildFun(jsonRes['data']);
  }
}
複製代碼

那麼在解析的位置能夠寫以下代碼:

var response = await HTTP.get(url);
Person data = BaseResp<Person>(response.body, (res) => Person.fromJson(res)).data;
///或者
Phone data = BaseResp<Phone>(response.body, (res) => Phone.fromJson(res)).data;
複製代碼

也就是說,經過構造BaseResp對象,指定data對象的泛型,並傳入data對象特有的fromJson構造函數,便可實現對象的建立,最後經過'.data'取值,便可獲得想要的特定類型的data。

固然,那一長串挺長的,每次寫這麼一大段也有點麻煩,能夠把這一行代碼作成代碼模板,只留下泛型部分做爲模板變量便可。

更新:

有小夥伴指出,上面的BaseResp並不能很好地處理json中data的類型爲數組的狀況,也就是是相似: {"code":0,"msg":"success","data":[{"name":"debuggerx","age":26},{"name":"dx","age":27}]} data的類型爲List<Person>這種狀況。此時能夠參考issue 11擴充一個BaseRespList便可解決這個問題。

從 0 到 1:個人 Flutter 技術實踐 | 掘金技術徵文,徵文活動正在進行中

相關文章
相關標籤/搜索