├── Example ├── json.gif ├── parse.gif ├── pyqt571.png └── use.gif ├── README.md ├── Test ├── EmptyResp.dart ├── EmptyTest.json ├── IgnoreMapResp.dart ├── IgnoreMapTest.json ├── ListTopResp.dart ├── ListTopTest.json ├── ListWithStringResp.dart ├── ListWithStringTest.json ├── ListsResp.dart ├── ListsTest.json ├── RegionResp.dart ├── RegionTest.json ├── WanResp.dart ├── WanTest.json ├── pubspec.lock ├── pubspec.yaml ├── test.dart └── test.sh ├── check_version.py ├── formatter.py ├── logo.ico ├── logo.png ├── logo.qrc ├── logo_rc.py ├── mainwindow.py ├── mainwindow.ui ├── template_code.py ├── tools.py └── version /Example/json.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debuggerx01/JSONFormat4Flutter/5b24ca47bce33524e0c1919c87f7c76a1457f8fb/Example/json.gif -------------------------------------------------------------------------------- /Example/parse.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debuggerx01/JSONFormat4Flutter/5b24ca47bce33524e0c1919c87f7c76a1457f8fb/Example/parse.gif -------------------------------------------------------------------------------- /Example/pyqt571.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debuggerx01/JSONFormat4Flutter/5b24ca47bce33524e0c1919c87f7c76a1457f8fb/Example/pyqt571.png -------------------------------------------------------------------------------- /Example/use.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debuggerx01/JSONFormat4Flutter/5b24ca47bce33524e0c1919c87f7c76a1457f8fb/Example/use.gif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JSONFormat4Flutter 2 | 受zzz40500/GsonFormat启发,将JSONObject格式的String解析成dart语言的实体类 3 | 4 | ## 本工具已迁移至空安全(2021–3–6) 5 | dart 语言更新为 null-safety 语法后,本工具生成的实体类代码就无法通过静态检查,具体细节可以参考[迁移Flutter项目到空安全的血泪史——有血、有泪、有💩](http://www.debuggerx.com/2021/03/07/migrate-flutter-project-to-null-safety/)。 6 | 7 | 主要的问题有: 8 | 1. 实例的变量需要在声明时初始化,或者在类的构造方法中进行初始化; 9 | 2. 类的工厂方法不允许返回 null; 10 | 3. 复杂逻辑下的可达性分析和空安全类型提升并不完美(例如对数组字段生成的赋值代码,循环中的 list 对象逻辑上不会为 null,但是语法检查器还是会将其判定为不安全的) 11 | 12 | 针对如上问题,本次更新做出如下修改: 13 | 1. 所有字段的类型均设置为可空,事实上由于我们无法保证 json 字符串输入的可靠性,不管设计如何确实所有字段都是有可能为空的; 14 | 2. 新增 `parse(jsonStr)` 静态方法,用于替代之前的工厂构造函数,推荐在项目中优先使用该方法进行解析; 15 | 3. 将数组字段的泛型也全部设置为可空的,并通过添加 `!` 标识符解决语法检查认为数组字段可能为空不可操作的问题。 16 | 17 | 除此之外,由于发现某些三方库会尝试调用对象的 `toJson()` 方法来实现打印输出或序列化,所以添加 `String toJson() => this.toString();` 的方法映射。 18 | 19 | See More: [JSONFormat4Flutter v0.9 更新说明](https://www.debuggerx.com/2021/04/20/jsonformat4flutter-v0-9-nullsafety/) 20 | 21 | 22 | ## 详细介绍说明(掘金):[在Flutter开发过程中快速生成json解析模板类的工具](https://juejin.im/post/5b4e04bbe51d45198c018e6e) 23 | 24 | ## 使用演示操作: 25 | 26 | ![](https://github.com/debuggerx01/JSONFormat4Flutter/blob/master/Example/json.gif?raw=true) 27 | 28 | 29 | ## 使用说明 30 | #### 1.界面操作 (参考录屏:[parse.gif](https://github.com/debuggerx01/JSONFormat4Flutter/blob/master/Example/parse.gif)) 31 | 1. 工具运行以后,先将复制好的json字符串粘贴到左侧文本框,然后点击'格式化'按钮;如果提示出错请检查json是否合法 32 | 2. 格式化成功后左侧json将会按照缩进格式化显示,并且右侧表格将显示分析得出的json结构,'Fields'列显示层级和原始分析数据,'Name'列显示每个字段的名称,'Type'列用于设定字段的数据类型 33 | 1. 对于普通数据类型(int、 double、 boolean、 String),Types列的类型将会自动给出,请尽量避免在上面滚动鼠标滚轮导致类型选择改变 34 | 2. 对于值null的字段,Types列的类型会自动设置为Object,并以黄色背景作为警告。此时如果直接生成代码也是可以使用的,只是该字段在使用时可能需要手动强转,所以建议在知晓该字段实际类型情况下尽量补全json字符串后再点击'格式化',或者在类型下拉框中指定实际的基本数据类型 35 | 3. 对于自定义对象类型(或者说字典/Map),'Fields'的对应输入框将留空并设为红色背景,需要您手动输入类型名称,并请注意: 36 | 1. 任意一个字段没有输入类型名时点击代码生成按钮,都将弹出警告提示并拒绝生成代码 37 | 2. 设置类型名时可以参考同一行'Name'栏的值进行设置以方便使用时识别字段,一般情况下推荐直接将'Name'栏内容首字母大写作为类型名 38 | 3. 但是需要注意,类型名不可与'Name'栏内容完全相同,且不能是dart中的关键字,否则生成的代码将包含语法错误 39 | 4. 一般情况下第一行的数据类型为对象且'Name'栏内容为空,设置第一列的'Types'即为生成的bean的顶级对象类名,推荐使用'该json的作用+Resp/Bean'形式进行命名以方便管理 40 | 4. 对于数组类型,'Types'栏将被自动设置,并且: 41 | 1. 数组的泛型类型取决于数组的内容的类型,也就是下一行设置的类型;当数组下一行的内容类型变化时泛型也会自动改变 42 | 2. 支持数组的嵌套泛型传递 43 | 3. 支持空数组,并且生成的代码中其泛型会被设置为dynamic 44 | 5. 特殊的,如果json本身的顶层级不是对象而是数组,那么需要为第一行的'Name'栏设置类型名称,获取顶层级数组数据的方式为对象bean.list 45 | 3. 确认设置无误后,点击'生成Bean'按钮,左侧json显示栏的内容将被替换为生成的代码,可以使用鼠标键盘全选复制,或者直接点击下方的'复制'按钮,然后将代码粘贴到IDE中,完成解析流程 46 | 47 | 48 | #### 2.生成代码说明 (参考录屏:[use.gif](https://github.com/debuggerx01/JSONFormat4Flutter/blob/master/Example/use.gif)) 49 | 50 | 1. 反序列化(json字符串->对象) 51 |
将生成的代码粘贴到dart源文件中后,即可以在任意地方导包使用,一般方法为(以http.get请求为例): 52 | ```java 53 | var response = await HTTP.get(url); 54 | var resp = BeanResp(response.body); 55 | ``` 56 | 也就是说,将请求到的json内容作为参数传递给BeanResp的默认构造函数,这样生成的resp对象即是请求到内容的实体。 57 | 需要说明的是,默认构造既可以传入json的原始字符串,也可以传入已经用原生json.decode()方法解析过的json对象(这主要是为了照顾使用dio库进行数据请求时结果数据会被自动解析成json对象的情况)。 58 | 只有顶级对象拥有默认构造方法,而其他子层级对象将使用xxx.fromJson()的命名构造进行对象创建。 59 | 60 | 2. 序列化(对象->json字符串) 61 |
与官方样例的处理方式不同,直接调用对象的toString()方法即可得到json字符串完成序列化操作 62 | 63 | 3. 手动创建对象 64 |
为了方便大部分使用场景下的便利性,bean的默认构造函数被用来实现反序列化,所以如果想要在代码中手动传参创建bean对象,可以使用xxx.fromParams()命名构造来完成。 65 | 66 | ## 简易运行方式: 67 | 在 [Release](https://github.com/debuggerx01/JSONFormat4Flutter/releases) 页面中,选择下载对应平台最新的二进制文件后—— 68 | #### linux: 69 | 在程序目录打开终端后执行:chmod u+x Formatter_linux && ./Formatter_linux 70 | #### mac: 71 | 在程序目录打开终端后执行:chmod u+x Formatter_mac && ./Formatter_mac 72 | #### windows: 73 | 直接双击运行 Formatter_win.exe 74 | ## 源码运行(以MAC为例) 75 | 没有python运行环境的用户需要先安装python 76 | 77 | mac中可以使用如下命令安装 78 | ``` 79 | brew install python3 80 | brew install pip3 81 | ``` 82 | pip3是python3的包管理工具 83 | 84 | brew 可以参考下面的链接 85 | 86 | https://brew.sh/index_zh-cn 87 | 88 | 89 | 90 | 运行库的时候会可能会提示 91 | ``` 92 | Traceback (most recent call last): 93 | File "formater.py", line 8, in 94 | from mainwindow import * 95 | File "/Users/cjl/IdeaProjects/flutter/sxw-flutter-app/JSONFormat4Flutter/mainwindow.py", line 9, in 96 | from PyQt5 import QtCore, QtGui, QtWidgets 97 | ModuleNotFoundError: No module named 'PyQt5' 98 | 99 | ``` 100 | 101 | 这时候可以直接用 102 | `pip3 install PyQt5` 103 | `pip3 install pyperclip` 104 | 等待安装完成 105 | 106 | ``(注:brew安装最新版python3可能会出现ssl模块丢失导致pip3无法正常使用,此时也可以考虑直接在python官网下载pkg包方式安装python)`` 107 | 108 | 后面使用就是在命令行敲入 109 | `python3 formatter.py` 110 | 111 | ## 已知问题 112 | + mac下从文本框复制出的文字直接粘贴到 idea/android studio 中报错 " lllegal character '65279' " 113 | 114 | 参考 [issue1](https://github.com/debuggerx01/JSONFormat4Flutter/issues/1) ,如下图,使用5.7.1及之前版本的pyqt5 115 | 116 | ![](https://user-gold-cdn.xitu.io/2018/7/17/164a8c24460f41ee?w=1270&h=861&f=png&s=174844) 117 | 118 | ## build 119 | - Linux: 120 | ```shell 121 | nuitka3 --clang --standalone --windows-disable-console --linux-onefile-icon=logo.png --output-dir=output --show-progress --plugin-enable=qt-plugins --onefile formatter.py 122 | ``` 123 | - Windows: 124 | ```shell 125 | pyinstaller -F -w -i logo.ico formatter.py 126 | ``` 127 | -------------------------------------------------------------------------------- /Test/EmptyResp.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert' show json; 2 | 3 | class EmptyResp { 4 | Qwe? qwe; 5 | 6 | EmptyResp.fromParams({this.qwe}); 7 | 8 | factory EmptyResp(Object jsonStr) => jsonStr is String 9 | ? EmptyResp.fromJson(json.decode(jsonStr)) 10 | : EmptyResp.fromJson(jsonStr); 11 | 12 | static EmptyResp? parse(jsonStr) => 13 | ['null', '', null].contains(jsonStr) ? null : EmptyResp(jsonStr); 14 | 15 | EmptyResp.fromJson(jsonRes) { 16 | qwe = jsonRes['qwe'] == null ? null : Qwe.fromJson(jsonRes['qwe']); 17 | } 18 | 19 | @override 20 | String toString() { 21 | return '{"qwe": $qwe}'; 22 | } 23 | 24 | String toJson() => this.toString(); 25 | } 26 | 27 | class Qwe { 28 | List? asd; 29 | List? qaz; 30 | List? zxc; 31 | 32 | Qwe.fromParams({this.asd, this.qaz, this.zxc}); 33 | 34 | Qwe.fromJson(jsonRes) { 35 | asd = jsonRes['asd'] == null ? null : []; 36 | 37 | for (var asdItem in asd == null ? [] : jsonRes['asd']) { 38 | asd!.add(asdItem); 39 | } 40 | 41 | qaz = jsonRes['qaz'] == null ? null : []; 42 | 43 | for (var qazItem in qaz == null ? [] : jsonRes['qaz']) { 44 | qaz!.add(qazItem); 45 | } 46 | 47 | zxc = jsonRes['zxc'] == null ? null : []; 48 | 49 | for (var zxcItem in zxc == null ? [] : jsonRes['zxc']) { 50 | zxc!.add(zxcItem); 51 | } 52 | } 53 | 54 | @override 55 | String toString() { 56 | return '{"asd": $asd, "qaz": $qaz, "zxc": $zxc}'; 57 | } 58 | 59 | String toJson() => this.toString(); 60 | } 61 | -------------------------------------------------------------------------------- /Test/EmptyTest.json: -------------------------------------------------------------------------------- 1 | { 2 | "qwe":{ 3 | "asd":[], 4 | "zxc":[], 5 | "qaz":[{}] 6 | } 7 | } -------------------------------------------------------------------------------- /Test/IgnoreMapResp.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert' show json; 2 | 3 | class IgnoreMapResp { 4 | Data? data; 5 | 6 | IgnoreMapResp.fromParams({this.data}); 7 | 8 | factory IgnoreMapResp(Object jsonStr) => jsonStr is String 9 | ? IgnoreMapResp.fromJson(json.decode(jsonStr)) 10 | : IgnoreMapResp.fromJson(jsonStr); 11 | 12 | static IgnoreMapResp? parse(jsonStr) => 13 | ['null', '', null].contains(jsonStr) ? null : IgnoreMapResp(jsonStr); 14 | 15 | IgnoreMapResp.fromJson(jsonRes) { 16 | data = jsonRes['data'] == null ? null : Data.fromJson(jsonRes['data']); 17 | } 18 | 19 | @override 20 | String toString() { 21 | return '{"data": $data}'; 22 | } 23 | 24 | String toJson() => this.toString(); 25 | } 26 | 27 | class Data { 28 | int? wc; 29 | String? author; 30 | String? content; 31 | String? digest; 32 | String? title; 33 | Map? date; 34 | Extra? extra; 35 | 36 | Data.fromParams( 37 | {this.wc, 38 | this.author, 39 | this.content, 40 | this.digest, 41 | this.title, 42 | this.date, 43 | this.extra}); 44 | 45 | Data.fromJson(jsonRes) { 46 | wc = jsonRes['wc']; 47 | author = jsonRes['author']; 48 | content = jsonRes['content']; 49 | digest = jsonRes['digest']; 50 | title = jsonRes['title']; 51 | date = jsonRes['date']; 52 | extra = jsonRes['extra'] == null ? null : Extra.fromJson(jsonRes['extra']); 53 | } 54 | 55 | @override 56 | String toString() { 57 | return '{"wc": $wc, "author": ${author != null ? '${json.encode(author)}' : 'null'}, "content": ${content != null ? '${json.encode(content)}' : 'null'}, "digest": ${digest != null ? '${json.encode(digest)}' : 'null'}, "title": ${title != null ? '${json.encode(title)}' : 'null'}, "date": ${date != null ? '${json.encode(date)}' : 'null'}, "extra": $extra}'; 58 | } 59 | 60 | String toJson() => this.toString(); 61 | } 62 | 63 | class Extra { 64 | int? a; 65 | int? b; 66 | int? c; 67 | 68 | Extra.fromParams({this.a, this.b, this.c}); 69 | 70 | Extra.fromJson(jsonRes) { 71 | a = jsonRes['a']; 72 | b = jsonRes['b']; 73 | c = jsonRes['c']; 74 | } 75 | 76 | @override 77 | String toString() { 78 | return '{"a": $a, "b": $b, "c": $c}'; 79 | } 80 | 81 | String toJson() => this.toString(); 82 | } 83 | -------------------------------------------------------------------------------- /Test/IgnoreMapTest.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "author": "顾湘", 4 | "content": "content", 5 | "date": { 6 | "curr": "20180216", 7 | "next": "20180217", 8 | "prev": "20180215" 9 | }, 10 | "extra":{ 11 | "a": 1, 12 | "b": 2, 13 | "c": 3 14 | }, 15 | "digest": "digest", 16 | "title": "新年的求婚者", 17 | "wc": 845 18 | } 19 | } -------------------------------------------------------------------------------- /Test/ListTopResp.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert' show json; 2 | 3 | class ListTopResp { 4 | List? list; 5 | 6 | ListTopResp.fromParams({this.list}); 7 | 8 | factory ListTopResp(Object jsonStr) => jsonStr is String 9 | ? ListTopResp.fromJson(json.decode(jsonStr)) 10 | : ListTopResp.fromJson(jsonStr); 11 | 12 | static ListTopResp? parse(jsonStr) => 13 | ['null', '', null].contains(jsonStr) ? null : ListTopResp(jsonStr); 14 | 15 | ListTopResp.fromJson(jsonRes) { 16 | list = jsonRes == null ? null : []; 17 | 18 | for (var listItem in list == null ? [] : jsonRes) { 19 | list!.add(listItem == null ? null : Resp.fromJson(listItem)); 20 | } 21 | } 22 | 23 | @override 24 | String toString() { 25 | return '{"json_list": $list}'; 26 | } 27 | 28 | String toJson() => this.toString(); 29 | } 30 | 31 | class Resp { 32 | String? a; 33 | String? b; 34 | 35 | Resp.fromParams({this.a, this.b}); 36 | 37 | Resp.fromJson(jsonRes) { 38 | a = jsonRes['a']; 39 | b = jsonRes['b']; 40 | } 41 | 42 | @override 43 | String toString() { 44 | return '{"a": ${a != null ? '${json.encode(a)}' : 'null'}, "b": ${b != null ? '${json.encode(b)}' : 'null'}}'; 45 | } 46 | 47 | String toJson() => this.toString(); 48 | } 49 | -------------------------------------------------------------------------------- /Test/ListTopTest.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "a": "xx", 4 | "b": "bbb" 5 | }, 6 | { 7 | "a": "xx", 8 | "b": "bbbb" 9 | }, 10 | { 11 | "a": "xx", 12 | "b": "bbbbb" 13 | } 14 | ] -------------------------------------------------------------------------------- /Test/ListWithStringResp.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert' show json; 2 | 3 | class ListWithStringResp { 4 | List? asd; 5 | List?>? qaz; 6 | List? zxc; 7 | 8 | ListWithStringResp.fromParams({this.asd, this.qaz, this.zxc}); 9 | 10 | factory ListWithStringResp(Object jsonStr) => jsonStr is String 11 | ? ListWithStringResp.fromJson(json.decode(jsonStr)) 12 | : ListWithStringResp.fromJson(jsonStr); 13 | 14 | static ListWithStringResp? parse(jsonStr) => 15 | ['null', '', null].contains(jsonStr) ? null : ListWithStringResp(jsonStr); 16 | 17 | ListWithStringResp.fromJson(jsonRes) { 18 | asd = jsonRes['asd'] == null ? null : []; 19 | 20 | for (var asdItem in asd == null ? [] : jsonRes['asd']) { 21 | asd!.add(asdItem); 22 | } 23 | 24 | qaz = jsonRes['qaz'] == null ? null : []; 25 | 26 | for (var qazItem in qaz == null ? [] : jsonRes['qaz']) { 27 | List? qazChild = qazItem == null ? null : []; 28 | for (var qazItemItem in qazChild == null ? [] : qazItem) { 29 | qazChild!.add(qazItemItem); 30 | } 31 | qaz!.add(qazChild); 32 | } 33 | 34 | zxc = jsonRes['zxc'] == null ? null : []; 35 | 36 | for (var zxcItem in zxc == null ? [] : jsonRes['zxc']) { 37 | zxc!.add(zxcItem == null ? null : Qwe.fromJson(zxcItem)); 38 | } 39 | } 40 | 41 | @override 42 | String toString() { 43 | return '{"asd": ${asd != null ? '${json.encode(asd)}' : 'null'}, "qaz": ${qaz != null ? '${json.encode(qaz)}' : 'null'}, "zxc": $zxc}'; 44 | } 45 | 46 | String toJson() => this.toString(); 47 | } 48 | 49 | class Qwe { 50 | String? qwe; 51 | 52 | Qwe.fromParams({this.qwe}); 53 | 54 | Qwe.fromJson(jsonRes) { 55 | qwe = jsonRes['qwe']; 56 | } 57 | 58 | @override 59 | String toString() { 60 | return '{"qwe": ${qwe != null ? '${json.encode(qwe)}' : 'null'}}'; 61 | } 62 | 63 | String toJson() => this.toString(); 64 | } 65 | -------------------------------------------------------------------------------- /Test/ListWithStringTest.json: -------------------------------------------------------------------------------- 1 | { 2 | "asd": ["asd", "zxc", "qwe"], 3 | "qaz": [["qaz"]], 4 | "zxc": [{"qwe": "qwe"}] 5 | } -------------------------------------------------------------------------------- /Test/ListsResp.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert' show json; 2 | 3 | class ListsResp { 4 | List?>?>? asd; 5 | List? qaz; 6 | List?>?>? qwe; 7 | 8 | ListsResp.fromParams({this.asd, this.qaz, this.qwe}); 9 | 10 | factory ListsResp(Object jsonStr) => jsonStr is String 11 | ? ListsResp.fromJson(json.decode(jsonStr)) 12 | : ListsResp.fromJson(jsonStr); 13 | 14 | static ListsResp? parse(jsonStr) => 15 | ['null', '', null].contains(jsonStr) ? null : ListsResp(jsonStr); 16 | 17 | ListsResp.fromJson(jsonRes) { 18 | asd = jsonRes['asd'] == null ? null : []; 19 | 20 | for (var asdItem in asd == null ? [] : jsonRes['asd']) { 21 | List?>? asdChild = asdItem == null ? null : []; 22 | for (var asdItemItem in asdChild == null ? [] : asdItem) { 23 | List? asdChildChild = asdItemItem == null ? null : []; 24 | for (var asdItemItemItem in asdChildChild == null ? [] : asdItemItem) { 25 | asdChildChild!.add(asdItemItemItem); 26 | } 27 | asdChild!.add(asdChildChild); 28 | } 29 | asd!.add(asdChild); 30 | } 31 | 32 | qaz = jsonRes['qaz'] == null ? null : []; 33 | 34 | for (var qazItem in qaz == null ? [] : jsonRes['qaz']) { 35 | qaz!.add(qazItem); 36 | } 37 | 38 | qwe = jsonRes['qwe'] == null ? null : []; 39 | 40 | for (var qweItem in qwe == null ? [] : jsonRes['qwe']) { 41 | List?>? qweChild = qweItem == null ? null : []; 42 | for (var qweItemItem in qweChild == null ? [] : qweItem) { 43 | List? qweChildChild = qweItemItem == null ? null : []; 44 | for (var qweItemItemItem in qweChildChild == null ? [] : qweItemItem) { 45 | qweChildChild!.add( 46 | qweItemItemItem == null ? null : Zxc.fromJson(qweItemItemItem)); 47 | } 48 | qweChild!.add(qweChildChild); 49 | } 50 | qwe!.add(qweChild); 51 | } 52 | } 53 | 54 | @override 55 | String toString() { 56 | return '{"asd": $asd, "qaz": $qaz, "qwe": $qwe}'; 57 | } 58 | 59 | String toJson() => this.toString(); 60 | } 61 | 62 | class Zxc { 63 | int? zxc; 64 | 65 | Zxc.fromParams({this.zxc}); 66 | 67 | Zxc.fromJson(jsonRes) { 68 | zxc = jsonRes['zxc']; 69 | } 70 | 71 | @override 72 | String toString() { 73 | return '{"zxc": $zxc}'; 74 | } 75 | 76 | String toJson() => this.toString(); 77 | } 78 | -------------------------------------------------------------------------------- /Test/ListsTest.json: -------------------------------------------------------------------------------- 1 | { 2 | "asd": [ 3 | [ 4 | [ 5 | 1 6 | ] 7 | ] 8 | ], 9 | "qwe": [ 10 | [ 11 | [ 12 | { 13 | "zxc": 1 14 | } 15 | ] 16 | ] 17 | ], 18 | "qaz": [ 19 | 1 20 | ] 21 | } -------------------------------------------------------------------------------- /Test/RegionResp.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert' show json; 2 | 3 | class RegionResp { 4 | int? code; 5 | int? ttl; 6 | String? message; 7 | Data? data; 8 | 9 | RegionResp.fromParams({this.code, this.ttl, this.message, this.data}); 10 | 11 | factory RegionResp(Object jsonStr) => jsonStr is String 12 | ? RegionResp.fromJson(json.decode(jsonStr)) 13 | : RegionResp.fromJson(jsonStr); 14 | 15 | static RegionResp? parse(jsonStr) => 16 | ['null', '', null].contains(jsonStr) ? null : RegionResp(jsonStr); 17 | 18 | RegionResp.fromJson(jsonRes) { 19 | code = jsonRes['code']; 20 | ttl = jsonRes['ttl']; 21 | message = jsonRes['message']; 22 | data = jsonRes['data'] == null ? null : Data.fromJson(jsonRes['data']); 23 | } 24 | 25 | @override 26 | String toString() { 27 | return '{"code": $code, "ttl": $ttl, "message": ${message != null ? '${json.encode(message)}' : 'null'}, "data": $data}'; 28 | } 29 | 30 | String toJson() => this.toString(); 31 | } 32 | 33 | class Data { 34 | List? archives; 35 | Page? page; 36 | 37 | Data.fromParams({this.archives, this.page}); 38 | 39 | Data.fromJson(jsonRes) { 40 | archives = jsonRes['archives'] == null ? null : []; 41 | 42 | for (var archivesItem in archives == null ? [] : jsonRes['archives']) { 43 | archives!.add(archivesItem == null ? null : Arch.fromJson(archivesItem)); 44 | } 45 | 46 | page = jsonRes['page'] == null ? null : Page.fromJson(jsonRes['page']); 47 | } 48 | 49 | @override 50 | String toString() { 51 | return '{"archives": $archives, "page": $page}'; 52 | } 53 | 54 | String toJson() => this.toString(); 55 | } 56 | 57 | class Page { 58 | int? count; 59 | int? num; 60 | int? size; 61 | 62 | Page.fromParams({this.count, this.num, this.size}); 63 | 64 | Page.fromJson(jsonRes) { 65 | count = jsonRes['count']; 66 | num = jsonRes['num']; 67 | size = jsonRes['size']; 68 | } 69 | 70 | @override 71 | String toString() { 72 | return '{"count": $count, "num": $num, "size": $size}'; 73 | } 74 | 75 | String toJson() => this.toString(); 76 | } 77 | 78 | class Arch { 79 | int? aid; 80 | int? attribute; 81 | int? copyright; 82 | int? ctime; 83 | int? duration; 84 | int? pubdate; 85 | int? state; 86 | int? tid; 87 | int? videos; 88 | String? desc; 89 | String? dynamic; 90 | String? pic; 91 | String? title; 92 | String? tname; 93 | Owner? owner; 94 | Right? rights; 95 | Stat? stat; 96 | 97 | Arch.fromParams( 98 | {this.aid, 99 | this.attribute, 100 | this.copyright, 101 | this.ctime, 102 | this.duration, 103 | this.pubdate, 104 | this.state, 105 | this.tid, 106 | this.videos, 107 | this.desc, 108 | this.dynamic, 109 | this.pic, 110 | this.title, 111 | this.tname, 112 | this.owner, 113 | this.rights, 114 | this.stat}); 115 | 116 | Arch.fromJson(jsonRes) { 117 | aid = jsonRes['aid']; 118 | attribute = jsonRes['attribute']; 119 | copyright = jsonRes['copyright']; 120 | ctime = jsonRes['ctime']; 121 | duration = jsonRes['duration']; 122 | pubdate = jsonRes['pubdate']; 123 | state = jsonRes['state']; 124 | tid = jsonRes['tid']; 125 | videos = jsonRes['videos']; 126 | desc = jsonRes['desc']; 127 | dynamic = jsonRes['dynamic']; 128 | pic = jsonRes['pic']; 129 | title = jsonRes['title']; 130 | tname = jsonRes['tname']; 131 | owner = jsonRes['owner'] == null ? null : Owner.fromJson(jsonRes['owner']); 132 | rights = 133 | jsonRes['rights'] == null ? null : Right.fromJson(jsonRes['rights']); 134 | stat = jsonRes['stat'] == null ? null : Stat.fromJson(jsonRes['stat']); 135 | } 136 | 137 | @override 138 | String toString() { 139 | return '{"aid": $aid, "attribute": $attribute, "copyright": $copyright, "ctime": $ctime, "duration": $duration, "pubdate": $pubdate, "state": $state, "tid": $tid, "videos": $videos, "desc": ${desc != null ? '${json.encode(desc)}' : 'null'}, "dynamic": ${dynamic != null ? '${json.encode(dynamic)}' : 'null'}, "pic": ${pic != null ? '${json.encode(pic)}' : 'null'}, "title": ${title != null ? '${json.encode(title)}' : 'null'}, "tname": ${tname != null ? '${json.encode(tname)}' : 'null'}, "owner": $owner, "rights": $rights, "stat": $stat}'; 140 | } 141 | 142 | String toJson() => this.toString(); 143 | } 144 | 145 | class Stat { 146 | int? aid; 147 | int? coin; 148 | int? danmaku; 149 | int? favorite; 150 | int? his_rank; 151 | int? like; 152 | int? now_rank; 153 | int? reply; 154 | int? share; 155 | int? view; 156 | 157 | Stat.fromParams( 158 | {this.aid, 159 | this.coin, 160 | this.danmaku, 161 | this.favorite, 162 | this.his_rank, 163 | this.like, 164 | this.now_rank, 165 | this.reply, 166 | this.share, 167 | this.view}); 168 | 169 | Stat.fromJson(jsonRes) { 170 | aid = jsonRes['aid']; 171 | coin = jsonRes['coin']; 172 | danmaku = jsonRes['danmaku']; 173 | favorite = jsonRes['favorite']; 174 | his_rank = jsonRes['his_rank']; 175 | like = jsonRes['like']; 176 | now_rank = jsonRes['now_rank']; 177 | reply = jsonRes['reply']; 178 | share = jsonRes['share']; 179 | view = jsonRes['view']; 180 | } 181 | 182 | @override 183 | String toString() { 184 | return '{"aid": $aid, "coin": $coin, "danmaku": $danmaku, "favorite": $favorite, "his_rank": $his_rank, "like": $like, "now_rank": $now_rank, "reply": $reply, "share": $share, "view": $view}'; 185 | } 186 | 187 | String toJson() => this.toString(); 188 | } 189 | 190 | class Right { 191 | int? bp; 192 | int? download; 193 | int? elec; 194 | int? hd5; 195 | int? movie; 196 | int? no_reprint; 197 | int? pay; 198 | 199 | Right.fromParams( 200 | {this.bp, 201 | this.download, 202 | this.elec, 203 | this.hd5, 204 | this.movie, 205 | this.no_reprint, 206 | this.pay}); 207 | 208 | Right.fromJson(jsonRes) { 209 | bp = jsonRes['bp']; 210 | download = jsonRes['download']; 211 | elec = jsonRes['elec']; 212 | hd5 = jsonRes['hd5']; 213 | movie = jsonRes['movie']; 214 | no_reprint = jsonRes['no_reprint']; 215 | pay = jsonRes['pay']; 216 | } 217 | 218 | @override 219 | String toString() { 220 | return '{"bp": $bp, "download": $download, "elec": $elec, "hd5": $hd5, "movie": $movie, "no_reprint": $no_reprint, "pay": $pay}'; 221 | } 222 | 223 | String toJson() => this.toString(); 224 | } 225 | 226 | class Owner { 227 | int? mid; 228 | String? face; 229 | String? name; 230 | 231 | Owner.fromParams({this.mid, this.face, this.name}); 232 | 233 | Owner.fromJson(jsonRes) { 234 | mid = jsonRes['mid']; 235 | face = jsonRes['face']; 236 | name = jsonRes['name']; 237 | } 238 | 239 | @override 240 | String toString() { 241 | return '{"mid": $mid, "face": ${face != null ? '${json.encode(face)}' : 'null'}, "name": ${name != null ? '${json.encode(name)}' : 'null'}}'; 242 | } 243 | 244 | String toJson() => this.toString(); 245 | } 246 | -------------------------------------------------------------------------------- /Test/RegionTest.json: -------------------------------------------------------------------------------- 1 | // 20180202124803 2 | // https://api.bilibili.com/x/web-interface/dynamic/region?rid=1&jsonp=jsonp 3 | 4 | { 5 | "code": 0, 6 | "data": { 7 | "page": { 8 | "num": 1, 9 | "size": 5, 10 | "count": 97 11 | }, 12 | "archives": [ 13 | { 14 | "aid": 18739254, 15 | "videos": 1, 16 | "tid": 27, 17 | "tname": "综合", 18 | "copyright": 1, 19 | "pic": "http://i0.hdslb.com/bfs/archive/20e3e01d4d1767eae633437133e665123ef085f9.jpg", 20 | "title": "(自制)全高达介绍 特别篇 结束战争不能靠一台高达! RX-78-系列介绍", 21 | "pubdate": 1516919462, 22 | "ctime": 1516919463, 23 | "desc": "视频类型: 盘点\n相关题材: 高达\n简介: 结束战争不能靠一台高达! 所以我们有一大堆~\n\n片头是个人渣剪出来的~", 24 | "state": 0, 25 | "attribute": 16384, 26 | "duration": 205, 27 | "rights": { 28 | "bp": 0, 29 | "elec": 0, 30 | "download": 0, 31 | "movie": 0, 32 | "pay": 0, 33 | "hd5": 0, 34 | "no_reprint": 0 35 | }, 36 | "owner": { 37 | "mid": 30124010, 38 | "name": "犬金组组长", 39 | "face": "http://i2.hdslb.com/bfs/face/11f217565887b25bb3a9ab515134043feab9eeeb.jpg" 40 | }, 41 | "stat": { 42 | "aid": 18739254, 43 | "view": 7100, 44 | "danmaku": 32, 45 | "reply": 154, 46 | "favorite": 190, 47 | "coin": 143, 48 | "share": 13, 49 | "now_rank": 0, 50 | "his_rank": 0, 51 | "like": 45 52 | }, 53 | "dynamic": "这个人居然更新了~" 54 | }, 55 | { 56 | "aid": 10681340, 57 | "videos": 1, 58 | "tid": 25, 59 | "tname": "MMD·3D", 60 | "copyright": 1, 61 | "pic": "http://i0.hdslb.com/bfs/archive/03212c07887f529cc4ed0643a3b4873628b516bc.jpg", 62 | "title": "【王者荣耀MMD】亮瑜乔:Barbwire【联动】", 63 | "pubdate": 1495224293, 64 | "ctime": 1497428928, 65 | "desc": "借物视频末。\n和水瓶的联动:av10725369(大概这就是周瑜绿吧2333)\n因为近些天下了很多的软件的缘故,电脑卡顿的可怕(。﹏。*)导出来前我也没想到会是这个样子。。颜色感觉没调好,还有镜头。。我的错(ㆀ˘・з・˘)\n话说风水轮流转,天美什么时候才加强都督啊(>﹏<)玩都督的话,看到对面有诸葛我都不敢走中单了( ﹁ ﹁ ) 犹记得被小天才支配的恐惧。。。\n最近连续爆肝了几个晚上才把模都改完了,求放过模型的一些小bug。。", 66 | "state": 0, 67 | "attribute": 49280, 68 | "duration": 88, 69 | "rights": { 70 | "bp": 0, 71 | "elec": 0, 72 | "download": 0, 73 | "movie": 0, 74 | "pay": 0, 75 | "hd5": 0, 76 | "no_reprint": 1 77 | }, 78 | "owner": { 79 | "mid": 5043239, 80 | "name": "landist", 81 | "face": "http://i2.hdslb.com/bfs/face/b68bd0260e19cf63f7da0a5cc70950f7ffcc925a.jpg" 82 | }, 83 | "stat": { 84 | "aid": 10681340, 85 | "view": 18929, 86 | "danmaku": 280, 87 | "reply": 327, 88 | "favorite": 1346, 89 | "coin": 113, 90 | "share": 132, 91 | "now_rank": 0, 92 | "his_rank": 0, 93 | "like": 11 94 | }, 95 | "dynamic": "" 96 | }, 97 | { 98 | "aid": 19023474, 99 | "videos": 1, 100 | "tid": 25, 101 | "tname": "MMD·3D", 102 | "copyright": 2, 103 | "pic": "http://i1.hdslb.com/bfs/archive/f7f9713572d5b7d2e83602e60637577a043baee0.jpg", 104 | "title": "【东方MMD】向星星许愿・・・", 105 | "pubdate": 1517511225, 106 | "ctime": 1517511226, 107 | "desc": "sm32674651\n原作者名: ちぇしゃ さん\n原视频标题: 【東方MMD】星に願いを・・・\n简介: 曾听人说真正想实现的愿望是实现不了的。\n不过「那是为了让人追寻才存在的」、\n正因如此人们才会以那没有终点的梦为目标不断前行着\n话说回来各位看到月全食了吗?我因为睡着了所以没能看到。\n这是曾几何时的『晚安了梅蒂』系列第3作了。\n『晚安了梅蒂』→sm28206725/av3822074\n『天赐恩宠』→sm30793336/av12797243", 108 | "state": 0, 109 | "attribute": 16384, 110 | "duration": 457, 111 | "rights": { 112 | "bp": 0, 113 | "elec": 0, 114 | "download": 0, 115 | "movie": 0, 116 | "pay": 0, 117 | "hd5": 0, 118 | "no_reprint": 0 119 | }, 120 | "owner": { 121 | "mid": 942357, 122 | "name": "R十寻", 123 | "face": "http://i0.hdslb.com/bfs/face/3533fab4c3d3c78c13b356d9e42d5cf3359eebd2.jpg" 124 | }, 125 | "stat": { 126 | "aid": 19023474, 127 | "view": 248, 128 | "danmaku": 73, 129 | "reply": 14, 130 | "favorite": 61, 131 | "coin": 10, 132 | "share": 1, 133 | "now_rank": 0, 134 | "his_rank": 0, 135 | "like": 36 136 | }, 137 | "dynamic": "" 138 | }, 139 | { 140 | "aid": 13663733, 141 | "videos": 1, 142 | "tid": 27, 143 | "tname": "综合", 144 | "copyright": 1, 145 | "pic": "http://i2.hdslb.com/bfs/archive/2e1e806b9f915fd7fae2298d912de8e2d2ea0bd8.jpg", 146 | "title": "我米老鼠今天也要唱HOP!欢迎来到真正的米♂奇♂妙♂妙♂屋!", 147 | "pubdate": 1503376629, 148 | "ctime": 1503376629, 149 | "desc": "突然来了灵感 感觉米奇唱HOP也挺可以的", 150 | "state": 0, 151 | "attribute": 49152, 152 | "duration": 112, 153 | "rights": { 154 | "bp": 0, 155 | "elec": 0, 156 | "download": 0, 157 | "movie": 0, 158 | "pay": 0, 159 | "hd5": 0, 160 | "no_reprint": 0 161 | }, 162 | "owner": { 163 | "mid": 8538903, 164 | "name": "二十四凯帝", 165 | "face": "http://i0.hdslb.com/bfs/face/69ca543356230237365c62009813bab9ac4321d3.jpg" 166 | }, 167 | "stat": { 168 | "aid": 13663733, 169 | "view": 12603, 170 | "danmaku": 160, 171 | "reply": 166, 172 | "favorite": 166, 173 | "coin": 183, 174 | "share": 189, 175 | "now_rank": 0, 176 | "his_rank": 0, 177 | "like": 85 178 | }, 179 | "dynamic": "" 180 | }, 181 | { 182 | "aid": 18732746, 183 | "videos": 1, 184 | "tid": 27, 185 | "tname": "综合", 186 | "copyright": 2, 187 | "pic": "http://i2.hdslb.com/bfs/archive/3e940ed91610b1d5752737e47371c5ad07c55eb0.jpg", 188 | "title": "《刀剑神域》第三季确认!为何Alicization篇精彩 爱丽丝计划", 189 | "pubdate": 1516904136, 190 | "ctime": 1516904136, 191 | "desc": "https://m.youtube.com/watch?t=72s&v=EglcFY1J_d8\n来自YouTube 台湾up主@亞次圓 的视频\n刀剑神域第三季制作决定。\n到底讲述了什么故事呢?\nUW 这一被刀剑小说党所津津乐道的部分究竟发生事?\n请看这个视频。", 192 | "state": 0, 193 | "attribute": 540672, 194 | "duration": 439, 195 | "rights": { 196 | "bp": 0, 197 | "elec": 0, 198 | "download": 0, 199 | "movie": 0, 200 | "pay": 0, 201 | "hd5": 0, 202 | "no_reprint": 0 203 | }, 204 | "owner": { 205 | "mid": 97237121, 206 | "name": "御坂纱雾", 207 | "face": "http://i1.hdslb.com/bfs/face/8aa55c2f2b22b619865520a5e3b1deea00a3e644.jpg" 208 | }, 209 | "stat": { 210 | "aid": 18732746, 211 | "view": 30196, 212 | "danmaku": 300, 213 | "reply": 567, 214 | "favorite": 855, 215 | "coin": 373, 216 | "share": 235, 217 | "now_rank": 0, 218 | "his_rank": 0, 219 | "like": 418 220 | }, 221 | "dynamic": "刀剑神域第三季制作决定。\n到底讲述了什么故事呢?" 222 | } 223 | ] 224 | }, 225 | "message": "0", 226 | "ttl": 1 227 | } -------------------------------------------------------------------------------- /Test/WanResp.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert' show json; 2 | 3 | class WanResp { 4 | int? errorCode; 5 | String? errorMsg; 6 | List? data; 7 | 8 | WanResp.fromParams({this.errorCode, this.errorMsg, this.data}); 9 | 10 | factory WanResp(Object jsonStr) => jsonStr is String 11 | ? WanResp.fromJson(json.decode(jsonStr)) 12 | : WanResp.fromJson(jsonStr); 13 | 14 | static WanResp? parse(jsonStr) => 15 | ['null', '', null].contains(jsonStr) ? null : WanResp(jsonStr); 16 | 17 | WanResp.fromJson(jsonRes) { 18 | errorCode = jsonRes['errorCode']; 19 | errorMsg = jsonRes['errorMsg']; 20 | data = jsonRes['data'] == null ? null : []; 21 | 22 | for (var dataItem in data == null ? [] : jsonRes['data']) { 23 | data!.add(dataItem == null ? null : Data.fromJson(dataItem)); 24 | } 25 | } 26 | 27 | @override 28 | String toString() { 29 | return '{"errorCode": $errorCode, "errorMsg": ${errorMsg != null ? '${json.encode(errorMsg)}' : 'null'}, "data": $data}'; 30 | } 31 | 32 | String toJson() => this.toString(); 33 | } 34 | 35 | class Data { 36 | int? courseId; 37 | int? id; 38 | int? order; 39 | int? parentChapterId; 40 | int? visible; 41 | String? name; 42 | List? children; 43 | 44 | Data.fromParams( 45 | {this.courseId, 46 | this.id, 47 | this.order, 48 | this.parentChapterId, 49 | this.visible, 50 | this.name, 51 | this.children}); 52 | 53 | Data.fromJson(jsonRes) { 54 | courseId = jsonRes['courseId']; 55 | id = jsonRes['id']; 56 | order = jsonRes['order']; 57 | parentChapterId = jsonRes['parentChapterId']; 58 | visible = jsonRes['visible']; 59 | name = jsonRes['name']; 60 | children = jsonRes['children'] == null ? null : []; 61 | 62 | for (var childrenItem in children == null ? [] : jsonRes['children']) { 63 | children! 64 | .add(childrenItem == null ? null : Children.fromJson(childrenItem)); 65 | } 66 | } 67 | 68 | @override 69 | String toString() { 70 | return '{"courseId": $courseId, "id": $id, "order": $order, "parentChapterId": $parentChapterId, "visible": $visible, "name": ${name != null ? '${json.encode(name)}' : 'null'}, "children": $children}'; 71 | } 72 | 73 | String toJson() => this.toString(); 74 | } 75 | 76 | class Children { 77 | int? courseId; 78 | int? id; 79 | int? order; 80 | int? parentChapterId; 81 | int? visible; 82 | String? name; 83 | List? children; 84 | 85 | Children.fromParams( 86 | {this.courseId, 87 | this.id, 88 | this.order, 89 | this.parentChapterId, 90 | this.visible, 91 | this.name, 92 | this.children}); 93 | 94 | Children.fromJson(jsonRes) { 95 | courseId = jsonRes['courseId']; 96 | id = jsonRes['id']; 97 | order = jsonRes['order']; 98 | parentChapterId = jsonRes['parentChapterId']; 99 | visible = jsonRes['visible']; 100 | name = jsonRes['name']; 101 | children = jsonRes['children'] == null ? null : []; 102 | 103 | for (var childrenItem in children == null ? [] : jsonRes['children']) { 104 | children!.add(childrenItem); 105 | } 106 | } 107 | 108 | @override 109 | String toString() { 110 | return '{"courseId": $courseId, "id": $id, "order": $order, "parentChapterId": $parentChapterId, "visible": $visible, "name": ${name != null ? '${json.encode(name)}' : 'null'}, "children": $children}'; 111 | } 112 | 113 | String toJson() => this.toString(); 114 | } 115 | -------------------------------------------------------------------------------- /Test/WanTest.json: -------------------------------------------------------------------------------- 1 | // 20180703033218 2 | // http://www.wanandroid.com/tree/json 3 | 4 | { 5 | "data": [ 6 | { 7 | "children": [ 8 | { 9 | "children": [ 10 | 11 | ], 12 | "courseId": 13, 13 | "id": 60, 14 | "name": "Android Studio相关", 15 | "order": 1000, 16 | "parentChapterId": 150, 17 | "visible": 1 18 | }, 19 | { 20 | "children": [ 21 | 22 | ], 23 | "courseId": 13, 24 | "id": 169, 25 | "name": "gradle", 26 | "order": 1001, 27 | "parentChapterId": 150, 28 | "visible": 1 29 | }, 30 | { 31 | "children": [ 32 | 33 | ], 34 | "courseId": 13, 35 | "id": 269, 36 | "name": "官方发布", 37 | "order": 1002, 38 | "parentChapterId": 150, 39 | "visible": 1 40 | } 41 | ], 42 | "courseId": 13, 43 | "id": 150, 44 | "name": "开发环境", 45 | "order": 1, 46 | "parentChapterId": 0, 47 | "visible": 1 48 | }, 49 | { 50 | "children": [ 51 | { 52 | "children": [ 53 | 54 | ], 55 | "courseId": 13, 56 | "id": 168, 57 | "name": "Drawable", 58 | "order": 5000, 59 | "parentChapterId": 167, 60 | "visible": 1 61 | }, 62 | { 63 | "children": [ 64 | 65 | ], 66 | "courseId": 13, 67 | "id": 172, 68 | "name": "deep link", 69 | "order": 5001, 70 | "parentChapterId": 167, 71 | "visible": 1 72 | }, 73 | { 74 | "children": [ 75 | 76 | ], 77 | "courseId": 13, 78 | "id": 198, 79 | "name": "基础概念", 80 | "order": 5002, 81 | "parentChapterId": 167, 82 | "visible": 1 83 | }, 84 | { 85 | "children": [ 86 | 87 | ], 88 | "courseId": 13, 89 | "id": 224, 90 | "name": "adb", 91 | "order": 5003, 92 | "parentChapterId": 167, 93 | "visible": 1 94 | }, 95 | { 96 | "children": [ 97 | 98 | ], 99 | "courseId": 13, 100 | "id": 240, 101 | "name": "字符编码", 102 | "order": 5004, 103 | "parentChapterId": 167, 104 | "visible": 1 105 | }, 106 | { 107 | "children": [ 108 | 109 | ], 110 | "courseId": 13, 111 | "id": 241, 112 | "name": "线程池", 113 | "order": 5005, 114 | "parentChapterId": 167, 115 | "visible": 1 116 | }, 117 | { 118 | "children": [ 119 | 120 | ], 121 | "courseId": 13, 122 | "id": 257, 123 | "name": "Span", 124 | "order": 5006, 125 | "parentChapterId": 167, 126 | "visible": 1 127 | }, 128 | { 129 | "children": [ 130 | 131 | ], 132 | "courseId": 13, 133 | "id": 306, 134 | "name": "多线程与并发", 135 | "order": 5007, 136 | "parentChapterId": 167, 137 | "visible": 1 138 | }, 139 | { 140 | "children": [ 141 | 142 | ], 143 | "courseId": 13, 144 | "id": 307, 145 | "name": "Apk诞生记", 146 | "order": 5008, 147 | "parentChapterId": 167, 148 | "visible": 1 149 | } 150 | ], 151 | "courseId": 13, 152 | "id": 167, 153 | "name": "基础知识", 154 | "order": 5, 155 | "parentChapterId": 0, 156 | "visible": 1 157 | }, 158 | { 159 | "children": [ 160 | { 161 | "children": [ 162 | 163 | ], 164 | "courseId": 13, 165 | "id": 10, 166 | "name": "Activity", 167 | "order": 10000, 168 | "parentChapterId": 9, 169 | "visible": 1 170 | }, 171 | { 172 | "children": [ 173 | 174 | ], 175 | "courseId": 13, 176 | "id": 15, 177 | "name": "Service", 178 | "order": 10001, 179 | "parentChapterId": 9, 180 | "visible": 1 181 | }, 182 | { 183 | "children": [ 184 | 185 | ], 186 | "courseId": 13, 187 | "id": 16, 188 | "name": "BroadcastReceiver", 189 | "order": 10002, 190 | "parentChapterId": 9, 191 | "visible": 1 192 | }, 193 | { 194 | "children": [ 195 | 196 | ], 197 | "courseId": 13, 198 | "id": 17, 199 | "name": "ContentProvider", 200 | "order": 10003, 201 | "parentChapterId": 9, 202 | "visible": 1 203 | }, 204 | { 205 | "children": [ 206 | 207 | ], 208 | "courseId": 13, 209 | "id": 19, 210 | "name": "Intent", 211 | "order": 10004, 212 | "parentChapterId": 9, 213 | "visible": 1 214 | }, 215 | { 216 | "children": [ 217 | 218 | ], 219 | "courseId": 13, 220 | "id": 40, 221 | "name": "Context", 222 | "order": 10005, 223 | "parentChapterId": 9, 224 | "visible": 1 225 | }, 226 | { 227 | "children": [ 228 | 229 | ], 230 | "courseId": 13, 231 | "id": 267, 232 | "name": "handler", 233 | "order": 10006, 234 | "parentChapterId": 9, 235 | "visible": 1 236 | } 237 | ], 238 | "courseId": 13, 239 | "id": 9, 240 | "name": "四大组件", 241 | "order": 10, 242 | "parentChapterId": 0, 243 | "visible": 1 244 | }, 245 | { 246 | "children": [ 247 | { 248 | "children": [ 249 | 250 | ], 251 | "courseId": 13, 252 | "id": 26, 253 | "name": "基础UI控件", 254 | "order": 15000, 255 | "parentChapterId": 25, 256 | "visible": 1 257 | }, 258 | { 259 | "children": [ 260 | 261 | ], 262 | "courseId": 13, 263 | "id": 27, 264 | "name": "ListView&GridView", 265 | "order": 15001, 266 | "parentChapterId": 25, 267 | "visible": 1 268 | }, 269 | { 270 | "children": [ 271 | 272 | ], 273 | "courseId": 13, 274 | "id": 121, 275 | "name": "ViewPager", 276 | "order": 15003, 277 | "parentChapterId": 25, 278 | "visible": 1 279 | }, 280 | { 281 | "children": [ 282 | 283 | ], 284 | "courseId": 13, 285 | "id": 124, 286 | "name": "Fragment", 287 | "order": 15004, 288 | "parentChapterId": 25, 289 | "visible": 1 290 | }, 291 | { 292 | "children": [ 293 | 294 | ], 295 | "courseId": 13, 296 | "id": 259, 297 | "name": "ScrollView", 298 | "order": 15005, 299 | "parentChapterId": 25, 300 | "visible": 1 301 | } 302 | ], 303 | "courseId": 13, 304 | "id": 25, 305 | "name": "常用控件", 306 | "order": 15, 307 | "parentChapterId": 0, 308 | "visible": 1 309 | }, 310 | { 311 | "children": [ 312 | { 313 | "children": [ 314 | 315 | ], 316 | "courseId": 13, 317 | "id": 30, 318 | "name": "Toast", 319 | "order": 20000, 320 | "parentChapterId": 29, 321 | "visible": 1 322 | }, 323 | { 324 | "children": [ 325 | 326 | ], 327 | "courseId": 13, 328 | "id": 31, 329 | "name": "Dialog", 330 | "order": 20001, 331 | "parentChapterId": 29, 332 | "visible": 1 333 | }, 334 | { 335 | "children": [ 336 | 337 | ], 338 | "courseId": 13, 339 | "id": 32, 340 | "name": "PopupWindow", 341 | "order": 20002, 342 | "parentChapterId": 29, 343 | "visible": 1 344 | }, 345 | { 346 | "children": [ 347 | 348 | ], 349 | "courseId": 13, 350 | "id": 33, 351 | "name": "Notification", 352 | "order": 20003, 353 | "parentChapterId": 29, 354 | "visible": 1 355 | }, 356 | { 357 | "children": [ 358 | 359 | ], 360 | "courseId": 13, 361 | "id": 190, 362 | "name": "震动", 363 | "order": 20004, 364 | "parentChapterId": 29, 365 | "visible": 1 366 | }, 367 | { 368 | "children": [ 369 | 370 | ], 371 | "courseId": 13, 372 | "id": 263, 373 | "name": "截屏", 374 | "order": 20005, 375 | "parentChapterId": 29, 376 | "visible": 1 377 | }, 378 | { 379 | "children": [ 380 | 381 | ], 382 | "courseId": 13, 383 | "id": 341, 384 | "name": "键盘", 385 | "order": 20006, 386 | "parentChapterId": 29, 387 | "visible": 1 388 | } 389 | ], 390 | "courseId": 13, 391 | "id": 29, 392 | "name": "用户交互", 393 | "order": 20, 394 | "parentChapterId": 0, 395 | "visible": 1 396 | }, 397 | { 398 | "children": [ 399 | { 400 | "children": [ 401 | 402 | ], 403 | "courseId": 13, 404 | "id": 98, 405 | "name": "WebView", 406 | "order": 12000, 407 | "parentChapterId": 34, 408 | "visible": 1 409 | }, 410 | { 411 | "children": [ 412 | 413 | ], 414 | "courseId": 13, 415 | "id": 67, 416 | "name": "网络基础", 417 | "order": 25000, 418 | "parentChapterId": 34, 419 | "visible": 1 420 | }, 421 | { 422 | "children": [ 423 | 424 | ], 425 | "courseId": 13, 426 | "id": 68, 427 | "name": "volley", 428 | "order": 25001, 429 | "parentChapterId": 34, 430 | "visible": 1 431 | }, 432 | { 433 | "children": [ 434 | 435 | ], 436 | "courseId": 13, 437 | "id": 69, 438 | "name": "okhttp", 439 | "order": 25002, 440 | "parentChapterId": 34, 441 | "visible": 1 442 | }, 443 | { 444 | "children": [ 445 | 446 | ], 447 | "courseId": 13, 448 | "id": 70, 449 | "name": "retrofit", 450 | "order": 25003, 451 | "parentChapterId": 34, 452 | "visible": 1 453 | }, 454 | { 455 | "children": [ 456 | 457 | ], 458 | "courseId": 13, 459 | "id": 71, 460 | "name": "数据解析", 461 | "order": 25004, 462 | "parentChapterId": 34, 463 | "visible": 1 464 | }, 465 | { 466 | "children": [ 467 | 468 | ], 469 | "courseId": 13, 470 | "id": 200, 471 | "name": "https", 472 | "order": 25005, 473 | "parentChapterId": 34, 474 | "visible": 1 475 | } 476 | ], 477 | "courseId": 13, 478 | "id": 34, 479 | "name": "网络访问", 480 | "order": 25, 481 | "parentChapterId": 0, 482 | "visible": 1 483 | }, 484 | { 485 | "children": [ 486 | { 487 | "children": [ 488 | 489 | ], 490 | "courseId": 13, 491 | "id": 87, 492 | "name": "图片加载库", 493 | "order": 6000, 494 | "parentChapterId": 72, 495 | "visible": 1 496 | }, 497 | { 498 | "children": [ 499 | 500 | ], 501 | "courseId": 13, 502 | "id": 86, 503 | "name": "图片处理", 504 | "order": 6000, 505 | "parentChapterId": 72, 506 | "visible": 1 507 | } 508 | ], 509 | "courseId": 13, 510 | "id": 72, 511 | "name": "图片加载", 512 | "order": 30, 513 | "parentChapterId": 0, 514 | "visible": 1 515 | }, 516 | { 517 | "children": [ 518 | { 519 | "children": [ 520 | 521 | ], 522 | "courseId": 13, 523 | "id": 90, 524 | "name": "数据库", 525 | "order": 7000, 526 | "parentChapterId": 35, 527 | "visible": 1 528 | }, 529 | { 530 | "children": [ 531 | 532 | ], 533 | "courseId": 13, 534 | "id": 89, 535 | "name": "app缓存相关", 536 | "order": 7000, 537 | "parentChapterId": 35, 538 | "visible": 1 539 | } 540 | ], 541 | "courseId": 13, 542 | "id": 35, 543 | "name": "数据存储", 544 | "order": 35, 545 | "parentChapterId": 0, 546 | "visible": 1 547 | }, 548 | { 549 | "children": [ 550 | { 551 | "children": [ 552 | 553 | ], 554 | "courseId": 13, 555 | "id": 92, 556 | "name": "属性动画", 557 | "order": 8000, 558 | "parentChapterId": 36, 559 | "visible": 1 560 | }, 561 | { 562 | "children": [ 563 | 564 | ], 565 | "courseId": 13, 566 | "id": 91, 567 | "name": "常规动画", 568 | "order": 8000, 569 | "parentChapterId": 36, 570 | "visible": 1 571 | }, 572 | { 573 | "children": [ 574 | 575 | ], 576 | "courseId": 13, 577 | "id": 188, 578 | "name": "转场动画", 579 | "order": 8000, 580 | "parentChapterId": 36, 581 | "visible": 1 582 | }, 583 | { 584 | "children": [ 585 | 586 | ], 587 | "courseId": 13, 588 | "id": 238, 589 | "name": "其他动画", 590 | "order": 8001, 591 | "parentChapterId": 36, 592 | "visible": 1 593 | } 594 | ], 595 | "courseId": 13, 596 | "id": 36, 597 | "name": "动画效果", 598 | "order": 40, 599 | "parentChapterId": 0, 600 | "visible": 1 601 | }, 602 | { 603 | "children": [ 604 | { 605 | "children": [ 606 | 607 | ], 608 | "courseId": 13, 609 | "id": 126, 610 | "name": "绘图相关", 611 | "order": 9000, 612 | "parentChapterId": 37, 613 | "visible": 1 614 | }, 615 | { 616 | "children": [ 617 | 618 | ], 619 | "courseId": 13, 620 | "id": 99, 621 | "name": "具体案例", 622 | "order": 9000, 623 | "parentChapterId": 37, 624 | "visible": 1 625 | }, 626 | { 627 | "children": [ 628 | 629 | ], 630 | "courseId": 13, 631 | "id": 93, 632 | "name": "基础知识", 633 | "order": 9000, 634 | "parentChapterId": 37, 635 | "visible": 1 636 | }, 637 | { 638 | "children": [ 639 | 640 | ], 641 | "courseId": 13, 642 | "id": 94, 643 | "name": "事件分发", 644 | "order": 9000, 645 | "parentChapterId": 37, 646 | "visible": 1 647 | }, 648 | { 649 | "children": [ 650 | 651 | ], 652 | "courseId": 13, 653 | "id": 134, 654 | "name": "SurfaceView", 655 | "order": 9000, 656 | "parentChapterId": 37, 657 | "visible": 1 658 | } 659 | ], 660 | "courseId": 13, 661 | "id": 37, 662 | "name": "自定义控件", 663 | "order": 45, 664 | "parentChapterId": 0, 665 | "visible": 1 666 | }, 667 | { 668 | "children": [ 669 | { 670 | "children": [ 671 | 672 | ], 673 | "courseId": 13, 674 | "id": 97, 675 | "name": "音视频", 676 | "order": 10000, 677 | "parentChapterId": 38, 678 | "visible": 1 679 | }, 680 | { 681 | "children": [ 682 | 683 | ], 684 | "courseId": 13, 685 | "id": 95, 686 | "name": "相机Camera", 687 | "order": 10000, 688 | "parentChapterId": 38, 689 | "visible": 1 690 | }, 691 | { 692 | "children": [ 693 | 694 | ], 695 | "courseId": 13, 696 | "id": 170, 697 | "name": "闹钟", 698 | "order": 10005, 699 | "parentChapterId": 38, 700 | "visible": 1 701 | } 702 | ], 703 | "courseId": 13, 704 | "id": 38, 705 | "name": "多媒体技术", 706 | "order": 50, 707 | "parentChapterId": 0, 708 | "visible": 1 709 | }, 710 | { 711 | "children": [ 712 | { 713 | "children": [ 714 | 715 | ], 716 | "courseId": 13, 717 | "id": 151, 718 | "name": "Vector", 719 | "order": 11000, 720 | "parentChapterId": 39, 721 | "visible": 1 722 | }, 723 | { 724 | "children": [ 725 | 726 | ], 727 | "courseId": 13, 728 | "id": 166, 729 | "name": "BottomSheet", 730 | "order": 11000, 731 | "parentChapterId": 39, 732 | "visible": 1 733 | }, 734 | { 735 | "children": [ 736 | 737 | ], 738 | "courseId": 13, 739 | "id": 165, 740 | "name": "Support Annotations", 741 | "order": 11000, 742 | "parentChapterId": 39, 743 | "visible": 1 744 | }, 745 | { 746 | "children": [ 747 | 748 | ], 749 | "courseId": 13, 750 | "id": 164, 751 | "name": "File Provider", 752 | "order": 11000, 753 | "parentChapterId": 39, 754 | "visible": 1 755 | }, 756 | { 757 | "children": [ 758 | 759 | ], 760 | "courseId": 13, 761 | "id": 142, 762 | "name": "ConstraintLayout", 763 | "order": 11000, 764 | "parentChapterId": 39, 765 | "visible": 1 766 | }, 767 | { 768 | "children": [ 769 | 770 | ], 771 | "courseId": 13, 772 | "id": 183, 773 | "name": "WebP", 774 | "order": 11000, 775 | "parentChapterId": 39, 776 | "visible": 1 777 | }, 778 | { 779 | "children": [ 780 | 781 | ], 782 | "courseId": 13, 783 | "id": 54, 784 | "name": "新的控件", 785 | "order": 11000, 786 | "parentChapterId": 39, 787 | "visible": 1 788 | }, 789 | { 790 | "children": [ 791 | 792 | ], 793 | "courseId": 13, 794 | "id": 55, 795 | "name": "新的API", 796 | "order": 11000, 797 | "parentChapterId": 39, 798 | "visible": 1 799 | }, 800 | { 801 | "children": [ 802 | 803 | ], 804 | "courseId": 13, 805 | "id": 179, 806 | "name": "Data Binding", 807 | "order": 11000, 808 | "parentChapterId": 39, 809 | "visible": 1 810 | }, 811 | { 812 | "children": [ 813 | 814 | ], 815 | "courseId": 13, 816 | "id": 100, 817 | "name": "RecyclerView", 818 | "order": 11000, 819 | "parentChapterId": 39, 820 | "visible": 1 821 | }, 822 | { 823 | "children": [ 824 | 825 | ], 826 | "courseId": 13, 827 | "id": 125, 828 | "name": "CoordinatorLayout", 829 | "order": 11000, 830 | "parentChapterId": 39, 831 | "visible": 1 832 | }, 833 | { 834 | "children": [ 835 | 836 | ], 837 | "courseId": 13, 838 | "id": 192, 839 | "name": "权限管理", 840 | "order": 11000, 841 | "parentChapterId": 39, 842 | "visible": 1 843 | }, 844 | { 845 | "children": [ 846 | 847 | ], 848 | "courseId": 13, 849 | "id": 193, 850 | "name": "分屏", 851 | "order": 11000, 852 | "parentChapterId": 39, 853 | "visible": 1 854 | }, 855 | { 856 | "children": [ 857 | 858 | ], 859 | "courseId": 13, 860 | "id": 140, 861 | "name": "dagger2", 862 | "order": 16000, 863 | "parentChapterId": 39, 864 | "visible": 1 865 | }, 866 | { 867 | "children": [ 868 | 869 | ], 870 | "courseId": 13, 871 | "id": 329, 872 | "name": "Android 8.0", 873 | "order": 16001, 874 | "parentChapterId": 39, 875 | "visible": 1 876 | }, 877 | { 878 | "children": [ 879 | 880 | ], 881 | "courseId": 13, 882 | "id": 332, 883 | "name": "嵌套滑动", 884 | "order": 16002, 885 | "parentChapterId": 39, 886 | "visible": 1 887 | }, 888 | { 889 | "children": [ 890 | 891 | ], 892 | "courseId": 13, 893 | "id": 334, 894 | "name": "Architecture Components", 895 | "order": 16003, 896 | "parentChapterId": 39, 897 | "visible": 1 898 | }, 899 | { 900 | "children": [ 901 | 902 | ], 903 | "courseId": 13, 904 | "id": 373, 905 | "name": "support-design", 906 | "order": 16004, 907 | "parentChapterId": 39, 908 | "visible": 1 909 | } 910 | ], 911 | "courseId": 13, 912 | "id": 39, 913 | "name": "5.+高新技术", 914 | "order": 55, 915 | "parentChapterId": 0, 916 | "visible": 0 917 | }, 918 | { 919 | "children": [ 920 | { 921 | "children": [ 922 | 923 | ], 924 | "courseId": 13, 925 | "id": 184, 926 | "name": "推送&即时通讯", 927 | "order": 12000, 928 | "parentChapterId": 53, 929 | "visible": 1 930 | }, 931 | { 932 | "children": [ 933 | 934 | ], 935 | "courseId": 13, 936 | "id": 185, 937 | "name": "组件化", 938 | "order": 12000, 939 | "parentChapterId": 53, 940 | "visible": 1 941 | }, 942 | { 943 | "children": [ 944 | 945 | ], 946 | "courseId": 13, 947 | "id": 74, 948 | "name": "反编译", 949 | "order": 12000, 950 | "parentChapterId": 53, 951 | "visible": 1 952 | }, 953 | { 954 | "children": [ 955 | 956 | ], 957 | "courseId": 13, 958 | "id": 186, 959 | "name": "沉浸式", 960 | "order": 12000, 961 | "parentChapterId": 53, 962 | "visible": 1 963 | }, 964 | { 965 | "children": [ 966 | 967 | ], 968 | "courseId": 13, 969 | "id": 195, 970 | "name": "设计模式", 971 | "order": 12000, 972 | "parentChapterId": 53, 973 | "visible": 1 974 | }, 975 | { 976 | "children": [ 977 | 978 | ], 979 | "courseId": 13, 980 | "id": 197, 981 | "name": "Native Crash", 982 | "order": 12000, 983 | "parentChapterId": 53, 984 | "visible": 1 985 | }, 986 | { 987 | "children": [ 988 | 989 | ], 990 | "courseId": 13, 991 | "id": 196, 992 | "name": "常见异常", 993 | "order": 12000, 994 | "parentChapterId": 53, 995 | "visible": 1 996 | }, 997 | { 998 | "children": [ 999 | 1000 | ], 1001 | "courseId": 13, 1002 | "id": 174, 1003 | "name": "增量更新", 1004 | "order": 12000, 1005 | "parentChapterId": 53, 1006 | "visible": 1 1007 | }, 1008 | { 1009 | "children": [ 1010 | 1011 | ], 1012 | "courseId": 13, 1013 | "id": 191, 1014 | "name": "数据采集与埋点", 1015 | "order": 12000, 1016 | "parentChapterId": 53, 1017 | "visible": 1 1018 | }, 1019 | { 1020 | "children": [ 1021 | 1022 | ], 1023 | "courseId": 13, 1024 | "id": 80, 1025 | "name": "Github用法进阶", 1026 | "order": 12000, 1027 | "parentChapterId": 53, 1028 | "visible": 1 1029 | }, 1030 | { 1031 | "children": [ 1032 | 1033 | ], 1034 | "courseId": 13, 1035 | "id": 146, 1036 | "name": "React Native", 1037 | "order": 12000, 1038 | "parentChapterId": 53, 1039 | "visible": 1 1040 | }, 1041 | { 1042 | "children": [ 1043 | 1044 | ], 1045 | "courseId": 13, 1046 | "id": 76, 1047 | "name": "项目架构", 1048 | "order": 12000, 1049 | "parentChapterId": 53, 1050 | "visible": 1 1051 | }, 1052 | { 1053 | "children": [ 1054 | 1055 | ], 1056 | "courseId": 13, 1057 | "id": 73, 1058 | "name": "面试相关", 1059 | "order": 12000, 1060 | "parentChapterId": 53, 1061 | "visible": 1 1062 | }, 1063 | { 1064 | "children": [ 1065 | 1066 | ], 1067 | "courseId": 13, 1068 | "id": 77, 1069 | "name": "响应式编程", 1070 | "order": 12000, 1071 | "parentChapterId": 53, 1072 | "visible": 1 1073 | }, 1074 | { 1075 | "children": [ 1076 | 1077 | ], 1078 | "courseId": 13, 1079 | "id": 78, 1080 | "name": "性能优化", 1081 | "order": 12000, 1082 | "parentChapterId": 53, 1083 | "visible": 1 1084 | }, 1085 | { 1086 | "children": [ 1087 | 1088 | ], 1089 | "courseId": 13, 1090 | "id": 79, 1091 | "name": "黑科技", 1092 | "order": 12000, 1093 | "parentChapterId": 53, 1094 | "visible": 1 1095 | }, 1096 | { 1097 | "children": [ 1098 | 1099 | ], 1100 | "courseId": 13, 1101 | "id": 81, 1102 | "name": "学习的正确姿势", 1103 | "order": 12000, 1104 | "parentChapterId": 53, 1105 | "visible": 1 1106 | }, 1107 | { 1108 | "children": [ 1109 | 1110 | ], 1111 | "courseId": 13, 1112 | "id": 61, 1113 | "name": "Android测试相关", 1114 | "order": 12000, 1115 | "parentChapterId": 53, 1116 | "visible": 1 1117 | }, 1118 | { 1119 | "children": [ 1120 | 1121 | ], 1122 | "courseId": 13, 1123 | "id": 159, 1124 | "name": "OpenGL ES", 1125 | "order": 12000, 1126 | "parentChapterId": 53, 1127 | "visible": 1 1128 | }, 1129 | { 1130 | "children": [ 1131 | 1132 | ], 1133 | "courseId": 13, 1134 | "id": 160, 1135 | "name": "热修复", 1136 | "order": 12000, 1137 | "parentChapterId": 53, 1138 | "visible": 1 1139 | }, 1140 | { 1141 | "children": [ 1142 | 1143 | ], 1144 | "courseId": 13, 1145 | "id": 75, 1146 | "name": "插件化", 1147 | "order": 12000, 1148 | "parentChapterId": 53, 1149 | "visible": 1 1150 | }, 1151 | { 1152 | "children": [ 1153 | 1154 | ], 1155 | "courseId": 13, 1156 | "id": 239, 1157 | "name": "Xposed", 1158 | "order": 12001, 1159 | "parentChapterId": 53, 1160 | "visible": 1 1161 | }, 1162 | { 1163 | "children": [ 1164 | 1165 | ], 1166 | "courseId": 13, 1167 | "id": 246, 1168 | "name": "保活", 1169 | "order": 12002, 1170 | "parentChapterId": 53, 1171 | "visible": 1 1172 | }, 1173 | { 1174 | "children": [ 1175 | 1176 | ], 1177 | "courseId": 13, 1178 | "id": 250, 1179 | "name": "直播", 1180 | "order": 12003, 1181 | "parentChapterId": 53, 1182 | "visible": 1 1183 | }, 1184 | { 1185 | "children": [ 1186 | 1187 | ], 1188 | "courseId": 13, 1189 | "id": 251, 1190 | "name": "OpenCV", 1191 | "order": 12004, 1192 | "parentChapterId": 53, 1193 | "visible": 1 1194 | }, 1195 | { 1196 | "children": [ 1197 | 1198 | ], 1199 | "courseId": 13, 1200 | "id": 262, 1201 | "name": "SDK开发", 1202 | "order": 12005, 1203 | "parentChapterId": 53, 1204 | "visible": 1 1205 | }, 1206 | { 1207 | "children": [ 1208 | 1209 | ], 1210 | "courseId": 13, 1211 | "id": 302, 1212 | "name": "ANR", 1213 | "order": 12006, 1214 | "parentChapterId": 53, 1215 | "visible": 1 1216 | }, 1217 | { 1218 | "children": [ 1219 | 1220 | ], 1221 | "courseId": 13, 1222 | "id": 364, 1223 | "name": "模块化", 1224 | "order": 12007, 1225 | "parentChapterId": 53, 1226 | "visible": 0 1227 | } 1228 | ], 1229 | "courseId": 13, 1230 | "id": 53, 1231 | "name": "热门专题", 1232 | "order": 60, 1233 | "parentChapterId": 0, 1234 | "visible": 0 1235 | }, 1236 | { 1237 | "children": [ 1238 | { 1239 | "children": [ 1240 | 1241 | ], 1242 | "courseId": 13, 1243 | "id": 133, 1244 | "name": "NFC", 1245 | "order": 14000, 1246 | "parentChapterId": 127, 1247 | "visible": 1 1248 | }, 1249 | { 1250 | "children": [ 1251 | 1252 | ], 1253 | "courseId": 13, 1254 | "id": 131, 1255 | "name": "蓝牙", 1256 | "order": 14000, 1257 | "parentChapterId": 127, 1258 | "visible": 1 1259 | }, 1260 | { 1261 | "children": [ 1262 | 1263 | ], 1264 | "courseId": 13, 1265 | "id": 132, 1266 | "name": "传感器", 1267 | "order": 14000, 1268 | "parentChapterId": 127, 1269 | "visible": 1 1270 | }, 1271 | { 1272 | "children": [ 1273 | 1274 | ], 1275 | "courseId": 13, 1276 | "id": 322, 1277 | "name": "指纹", 1278 | "order": 14001, 1279 | "parentChapterId": 127, 1280 | "visible": 1 1281 | } 1282 | ], 1283 | "courseId": 13, 1284 | "id": 127, 1285 | "name": "硬件模块", 1286 | "order": 70, 1287 | "parentChapterId": 0, 1288 | "visible": 1 1289 | }, 1290 | { 1291 | "children": [ 1292 | { 1293 | "children": [ 1294 | 1295 | ], 1296 | "courseId": 13, 1297 | "id": 182, 1298 | "name": "JNI编程", 1299 | "order": 15000, 1300 | "parentChapterId": 128, 1301 | "visible": 1 1302 | }, 1303 | { 1304 | "children": [ 1305 | 1306 | ], 1307 | "courseId": 13, 1308 | "id": 149, 1309 | "name": "so文件相关", 1310 | "order": 15000, 1311 | "parentChapterId": 128, 1312 | "visible": 1 1313 | } 1314 | ], 1315 | "courseId": 13, 1316 | "id": 128, 1317 | "name": "JNI", 1318 | "order": 75, 1319 | "parentChapterId": 0, 1320 | "visible": 1 1321 | }, 1322 | { 1323 | "children": [ 1324 | { 1325 | "children": [ 1326 | 1327 | ], 1328 | "courseId": 13, 1329 | "id": 139, 1330 | "name": "Crash捕获", 1331 | "order": 17000, 1332 | "parentChapterId": 130, 1333 | "visible": 1 1334 | }, 1335 | { 1336 | "children": [ 1337 | 1338 | ], 1339 | "courseId": 13, 1340 | "id": 137, 1341 | "name": "后端云", 1342 | "order": 17000, 1343 | "parentChapterId": 130, 1344 | "visible": 1 1345 | }, 1346 | { 1347 | "children": [ 1348 | 1349 | ], 1350 | "courseId": 13, 1351 | "id": 136, 1352 | "name": "地图", 1353 | "order": 17000, 1354 | "parentChapterId": 130, 1355 | "visible": 1 1356 | }, 1357 | { 1358 | "children": [ 1359 | 1360 | ], 1361 | "courseId": 13, 1362 | "id": 138, 1363 | "name": "推送", 1364 | "order": 17000, 1365 | "parentChapterId": 130, 1366 | "visible": 1 1367 | }, 1368 | { 1369 | "children": [ 1370 | 1371 | ], 1372 | "courseId": 13, 1373 | "id": 223, 1374 | "name": "ocr&图像识别", 1375 | "order": 17001, 1376 | "parentChapterId": 130, 1377 | "visible": 1 1378 | } 1379 | ], 1380 | "courseId": 13, 1381 | "id": 130, 1382 | "name": "常用SDK", 1383 | "order": 85, 1384 | "parentChapterId": 0, 1385 | "visible": 1 1386 | }, 1387 | { 1388 | "children": [ 1389 | { 1390 | "children": [ 1391 | 1392 | ], 1393 | "courseId": 13, 1394 | "id": 178, 1395 | "name": "apk安装", 1396 | "order": 18000, 1397 | "parentChapterId": 152, 1398 | "visible": 1 1399 | }, 1400 | { 1401 | "children": [ 1402 | 1403 | ], 1404 | "courseId": 13, 1405 | "id": 153, 1406 | "name": "Zygote进程启动", 1407 | "order": 18000, 1408 | "parentChapterId": 152, 1409 | "visible": 1 1410 | }, 1411 | { 1412 | "children": [ 1413 | 1414 | ], 1415 | "courseId": 13, 1416 | "id": 155, 1417 | "name": "SystemServer启动过程", 1418 | "order": 18000, 1419 | "parentChapterId": 152, 1420 | "visible": 1 1421 | }, 1422 | { 1423 | "children": [ 1424 | 1425 | ], 1426 | "courseId": 13, 1427 | "id": 173, 1428 | "name": "Choregrapher", 1429 | "order": 18000, 1430 | "parentChapterId": 152, 1431 | "visible": 1 1432 | }, 1433 | { 1434 | "children": [ 1435 | 1436 | ], 1437 | "courseId": 13, 1438 | "id": 171, 1439 | "name": "binder", 1440 | "order": 18000, 1441 | "parentChapterId": 152, 1442 | "visible": 1 1443 | }, 1444 | { 1445 | "children": [ 1446 | 1447 | ], 1448 | "courseId": 13, 1449 | "id": 233, 1450 | "name": "framework-四大组件", 1451 | "order": 18001, 1452 | "parentChapterId": 152, 1453 | "visible": 1 1454 | } 1455 | ], 1456 | "courseId": 13, 1457 | "id": 152, 1458 | "name": "framework", 1459 | "order": 90, 1460 | "parentChapterId": 0, 1461 | "visible": 1 1462 | }, 1463 | { 1464 | "children": [ 1465 | { 1466 | "children": [ 1467 | 1468 | ], 1469 | "courseId": 13, 1470 | "id": 135, 1471 | "name": "二维码", 1472 | "order": 16000, 1473 | "parentChapterId": 156, 1474 | "visible": 1 1475 | }, 1476 | { 1477 | "children": [ 1478 | 1479 | ], 1480 | "courseId": 13, 1481 | "id": 199, 1482 | "name": "MultiDex 启动优化", 1483 | "order": 19000, 1484 | "parentChapterId": 156, 1485 | "visible": 1 1486 | }, 1487 | { 1488 | "children": [ 1489 | 1490 | ], 1491 | "courseId": 13, 1492 | "id": 157, 1493 | "name": "获取设备唯一标识", 1494 | "order": 19000, 1495 | "parentChapterId": 156, 1496 | "visible": 1 1497 | }, 1498 | { 1499 | "children": [ 1500 | 1501 | ], 1502 | "courseId": 13, 1503 | "id": 163, 1504 | "name": "Splash页优化", 1505 | "order": 19000, 1506 | "parentChapterId": 156, 1507 | "visible": 1 1508 | }, 1509 | { 1510 | "children": [ 1511 | 1512 | ], 1513 | "courseId": 13, 1514 | "id": 158, 1515 | "name": "Fragment懒加载", 1516 | "order": 19000, 1517 | "parentChapterId": 156, 1518 | "visible": 1 1519 | }, 1520 | { 1521 | "children": [ 1522 | 1523 | ], 1524 | "courseId": 13, 1525 | "id": 222, 1526 | "name": "持续集成", 1527 | "order": 19001, 1528 | "parentChapterId": 156, 1529 | "visible": 1 1530 | }, 1531 | { 1532 | "children": [ 1533 | 1534 | ], 1535 | "courseId": 13, 1536 | "id": 228, 1537 | "name": "辅助 or 工具类", 1538 | "order": 19002, 1539 | "parentChapterId": 156, 1540 | "visible": 1 1541 | }, 1542 | { 1543 | "children": [ 1544 | 1545 | ], 1546 | "courseId": 13, 1547 | "id": 230, 1548 | "name": "打包", 1549 | "order": 19003, 1550 | "parentChapterId": 156, 1551 | "visible": 1 1552 | }, 1553 | { 1554 | "children": [ 1555 | 1556 | ], 1557 | "courseId": 13, 1558 | "id": 237, 1559 | "name": "下拉刷新&上拉加载", 1560 | "order": 19004, 1561 | "parentChapterId": 156, 1562 | "visible": 1 1563 | }, 1564 | { 1565 | "children": [ 1566 | 1567 | ], 1568 | "courseId": 13, 1569 | "id": 242, 1570 | "name": "实用插件", 1571 | "order": 19005, 1572 | "parentChapterId": 156, 1573 | "visible": 1 1574 | }, 1575 | { 1576 | "children": [ 1577 | 1578 | ], 1579 | "courseId": 13, 1580 | "id": 247, 1581 | "name": "防逆向", 1582 | "order": 19006, 1583 | "parentChapterId": 156, 1584 | "visible": 1 1585 | }, 1586 | { 1587 | "children": [ 1588 | 1589 | ], 1590 | "courseId": 13, 1591 | "id": 252, 1592 | "name": "奇怪的Bug", 1593 | "order": 19007, 1594 | "parentChapterId": 156, 1595 | "visible": 1 1596 | }, 1597 | { 1598 | "children": [ 1599 | 1600 | ], 1601 | "courseId": 13, 1602 | "id": 258, 1603 | "name": "三方分享 & 登录", 1604 | "order": 19008, 1605 | "parentChapterId": 156, 1606 | "visible": 1 1607 | }, 1608 | { 1609 | "children": [ 1610 | 1611 | ], 1612 | "courseId": 13, 1613 | "id": 260, 1614 | "name": "RxJava & Retrofit & MVP", 1615 | "order": 19009, 1616 | "parentChapterId": 156, 1617 | "visible": 1 1618 | }, 1619 | { 1620 | "children": [ 1621 | 1622 | ], 1623 | "courseId": 13, 1624 | "id": 261, 1625 | "name": "屏幕适配", 1626 | "order": 19010, 1627 | "parentChapterId": 156, 1628 | "visible": 1 1629 | }, 1630 | { 1631 | "children": [ 1632 | 1633 | ], 1634 | "courseId": 13, 1635 | "id": 268, 1636 | "name": "优秀的设计", 1637 | "order": 19011, 1638 | "parentChapterId": 156, 1639 | "visible": 1 1640 | }, 1641 | { 1642 | "children": [ 1643 | 1644 | ], 1645 | "courseId": 13, 1646 | "id": 270, 1647 | "name": "埋点", 1648 | "order": 19012, 1649 | "parentChapterId": 156, 1650 | "visible": 1 1651 | }, 1652 | { 1653 | "children": [ 1654 | 1655 | ], 1656 | "courseId": 13, 1657 | "id": 295, 1658 | "name": "混淆", 1659 | "order": 19013, 1660 | "parentChapterId": 156, 1661 | "visible": 1 1662 | }, 1663 | { 1664 | "children": [ 1665 | 1666 | ], 1667 | "courseId": 13, 1668 | "id": 335, 1669 | "name": "应用内更新", 1670 | "order": 19014, 1671 | "parentChapterId": 156, 1672 | "visible": 1 1673 | }, 1674 | { 1675 | "children": [ 1676 | 1677 | ], 1678 | "courseId": 13, 1679 | "id": 345, 1680 | "name": "国际化", 1681 | "order": 19015, 1682 | "parentChapterId": 156, 1683 | "visible": 1 1684 | }, 1685 | { 1686 | "children": [ 1687 | 1688 | ], 1689 | "courseId": 13, 1690 | "id": 379, 1691 | "name": "Android P 适配", 1692 | "order": 19016, 1693 | "parentChapterId": 156, 1694 | "visible": 1 1695 | } 1696 | ], 1697 | "courseId": 13, 1698 | "id": 156, 1699 | "name": "项目必备", 1700 | "order": 95, 1701 | "parentChapterId": 0, 1702 | "visible": 1 1703 | }, 1704 | { 1705 | "children": [ 1706 | { 1707 | "children": [ 1708 | 1709 | ], 1710 | "courseId": 13, 1711 | "id": 176, 1712 | "name": "个人博客", 1713 | "order": 20000, 1714 | "parentChapterId": 175, 1715 | "visible": 1 1716 | }, 1717 | { 1718 | "children": [ 1719 | 1720 | ], 1721 | "courseId": 13, 1722 | "id": 177, 1723 | "name": "公司对外", 1724 | "order": 20000, 1725 | "parentChapterId": 175, 1726 | "visible": 1 1727 | } 1728 | ], 1729 | "courseId": 13, 1730 | "id": 175, 1731 | "name": "推荐网站", 1732 | "order": 100, 1733 | "parentChapterId": 0, 1734 | "visible": 1 1735 | }, 1736 | { 1737 | "children": [ 1738 | { 1739 | "children": [ 1740 | 1741 | ], 1742 | "courseId": 13, 1743 | "id": 181, 1744 | "name": "javassist", 1745 | "order": 21000, 1746 | "parentChapterId": 180, 1747 | "visible": 1 1748 | }, 1749 | { 1750 | "children": [ 1751 | 1752 | ], 1753 | "courseId": 13, 1754 | "id": 202, 1755 | "name": "机器学习&人工智能", 1756 | "order": 21000, 1757 | "parentChapterId": 180, 1758 | "visible": 1 1759 | }, 1760 | { 1761 | "children": [ 1762 | 1763 | ], 1764 | "courseId": 13, 1765 | "id": 203, 1766 | "name": "javapoet", 1767 | "order": 21000, 1768 | "parentChapterId": 180, 1769 | "visible": 1 1770 | }, 1771 | { 1772 | "children": [ 1773 | 1774 | ], 1775 | "courseId": 13, 1776 | "id": 243, 1777 | "name": "JVM", 1778 | "order": 21001, 1779 | "parentChapterId": 180, 1780 | "visible": 1 1781 | }, 1782 | { 1783 | "children": [ 1784 | 1785 | ], 1786 | "courseId": 13, 1787 | "id": 273, 1788 | "name": "Android AR", 1789 | "order": 21002, 1790 | "parentChapterId": 180, 1791 | "visible": 1 1792 | }, 1793 | { 1794 | "children": [ 1795 | 1796 | ], 1797 | "courseId": 13, 1798 | "id": 296, 1799 | "name": "阅读", 1800 | "order": 21003, 1801 | "parentChapterId": 180, 1802 | "visible": 1 1803 | }, 1804 | { 1805 | "children": [ 1806 | 1807 | ], 1808 | "courseId": 13, 1809 | "id": 303, 1810 | "name": "区块链", 1811 | "order": 21004, 1812 | "parentChapterId": 180, 1813 | "visible": 1 1814 | }, 1815 | { 1816 | "children": [ 1817 | 1818 | ], 1819 | "courseId": 13, 1820 | "id": 318, 1821 | "name": "搭建博客", 1822 | "order": 21005, 1823 | "parentChapterId": 180, 1824 | "visible": 1 1825 | }, 1826 | { 1827 | "children": [ 1828 | 1829 | ], 1830 | "courseId": 13, 1831 | "id": 368, 1832 | "name": "计算机基础", 1833 | "order": 21006, 1834 | "parentChapterId": 180, 1835 | "visible": 1 1836 | } 1837 | ], 1838 | "courseId": 13, 1839 | "id": 180, 1840 | "name": "延伸技术", 1841 | "order": 105, 1842 | "parentChapterId": 0, 1843 | "visible": 1 1844 | }, 1845 | { 1846 | "children": [ 1847 | { 1848 | "children": [ 1849 | 1850 | ], 1851 | "courseId": 13, 1852 | "id": 227, 1853 | "name": "注解", 1854 | "order": 110000, 1855 | "parentChapterId": 225, 1856 | "visible": 1 1857 | }, 1858 | { 1859 | "children": [ 1860 | 1861 | ], 1862 | "courseId": 13, 1863 | "id": 229, 1864 | "name": "AOP", 1865 | "order": 110001, 1866 | "parentChapterId": 225, 1867 | "visible": 1 1868 | }, 1869 | { 1870 | "children": [ 1871 | 1872 | ], 1873 | "courseId": 13, 1874 | "id": 265, 1875 | "name": "反射", 1876 | "order": 110002, 1877 | "parentChapterId": 225, 1878 | "visible": 1 1879 | } 1880 | ], 1881 | "courseId": 13, 1882 | "id": 225, 1883 | "name": "注解 & 反射 & AOP", 1884 | "order": 110, 1885 | "parentChapterId": 0, 1886 | "visible": 1 1887 | }, 1888 | { 1889 | "children": [ 1890 | { 1891 | "children": [ 1892 | 1893 | ], 1894 | "courseId": 13, 1895 | "id": 232, 1896 | "name": "入门及知识点", 1897 | "order": 115000, 1898 | "parentChapterId": 231, 1899 | "visible": 1 1900 | } 1901 | ], 1902 | "courseId": 13, 1903 | "id": 231, 1904 | "name": "Kotlin", 1905 | "order": 115, 1906 | "parentChapterId": 0, 1907 | "visible": 1 1908 | }, 1909 | { 1910 | "children": [ 1911 | { 1912 | "children": [ 1913 | 1914 | ], 1915 | "courseId": 13, 1916 | "id": 245, 1917 | "name": "集合相关", 1918 | "order": 120000, 1919 | "parentChapterId": 244, 1920 | "visible": 1 1921 | }, 1922 | { 1923 | "children": [ 1924 | 1925 | ], 1926 | "courseId": 13, 1927 | "id": 304, 1928 | "name": "基础源码", 1929 | "order": 120001, 1930 | "parentChapterId": 244, 1931 | "visible": 1 1932 | }, 1933 | { 1934 | "children": [ 1935 | 1936 | ], 1937 | "courseId": 13, 1938 | "id": 308, 1939 | "name": "多线程", 1940 | "order": 120002, 1941 | "parentChapterId": 244, 1942 | "visible": 1 1943 | }, 1944 | { 1945 | "children": [ 1946 | 1947 | ], 1948 | "courseId": 13, 1949 | "id": 313, 1950 | "name": "字节码", 1951 | "order": 120003, 1952 | "parentChapterId": 244, 1953 | "visible": 1 1954 | }, 1955 | { 1956 | "children": [ 1957 | 1958 | ], 1959 | "courseId": 13, 1960 | "id": 317, 1961 | "name": "Lambda", 1962 | "order": 120004, 1963 | "parentChapterId": 244, 1964 | "visible": 1 1965 | }, 1966 | { 1967 | "children": [ 1968 | 1969 | ], 1970 | "courseId": 13, 1971 | "id": 320, 1972 | "name": "内存管理", 1973 | "order": 120005, 1974 | "parentChapterId": 244, 1975 | "visible": 1 1976 | }, 1977 | { 1978 | "children": [ 1979 | 1980 | ], 1981 | "courseId": 13, 1982 | "id": 321, 1983 | "name": "算法", 1984 | "order": 120006, 1985 | "parentChapterId": 244, 1986 | "visible": 1 1987 | }, 1988 | { 1989 | "children": [ 1990 | 1991 | ], 1992 | "courseId": 13, 1993 | "id": 346, 1994 | "name": "JVM", 1995 | "order": 120007, 1996 | "parentChapterId": 244, 1997 | "visible": 1 1998 | }, 1999 | { 2000 | "children": [ 2001 | 2002 | ], 2003 | "courseId": 13, 2004 | "id": 362, 2005 | "name": "泛型", 2006 | "order": 120008, 2007 | "parentChapterId": 244, 2008 | "visible": 1 2009 | } 2010 | ], 2011 | "courseId": 13, 2012 | "id": 244, 2013 | "name": "Java深入", 2014 | "order": 120, 2015 | "parentChapterId": 0, 2016 | "visible": 1 2017 | }, 2018 | { 2019 | "children": [ 2020 | { 2021 | "children": [ 2022 | 2023 | ], 2024 | "courseId": 13, 2025 | "id": 249, 2026 | "name": "干货资源", 2027 | "order": 125000, 2028 | "parentChapterId": 248, 2029 | "visible": 1 2030 | }, 2031 | { 2032 | "children": [ 2033 | 2034 | ], 2035 | "courseId": 13, 2036 | "id": 292, 2037 | "name": "pdf电子书", 2038 | "order": 125001, 2039 | "parentChapterId": 248, 2040 | "visible": 1 2041 | }, 2042 | { 2043 | "children": [ 2044 | 2045 | ], 2046 | "courseId": 13, 2047 | "id": 305, 2048 | "name": "各类工具", 2049 | "order": 125002, 2050 | "parentChapterId": 248, 2051 | "visible": 1 2052 | }, 2053 | { 2054 | "children": [ 2055 | 2056 | ], 2057 | "courseId": 13, 2058 | "id": 361, 2059 | "name": "课程推荐", 2060 | "order": 125003, 2061 | "parentChapterId": 248, 2062 | "visible": 1 2063 | } 2064 | ], 2065 | "courseId": 13, 2066 | "id": 248, 2067 | "name": "干货资源", 2068 | "order": 125, 2069 | "parentChapterId": 0, 2070 | "visible": 1 2071 | }, 2072 | { 2073 | "children": [ 2074 | { 2075 | "children": [ 2076 | 2077 | ], 2078 | "courseId": 13, 2079 | "id": 254, 2080 | "name": "新闻资讯", 2081 | "order": 130000, 2082 | "parentChapterId": 253, 2083 | "visible": 1 2084 | }, 2085 | { 2086 | "children": [ 2087 | 2088 | ], 2089 | "courseId": 13, 2090 | "id": 255, 2091 | "name": "工具类", 2092 | "order": 130001, 2093 | "parentChapterId": 253, 2094 | "visible": 1 2095 | }, 2096 | { 2097 | "children": [ 2098 | 2099 | ], 2100 | "courseId": 13, 2101 | "id": 256, 2102 | "name": "音乐、视频类", 2103 | "order": 130002, 2104 | "parentChapterId": 253, 2105 | "visible": 1 2106 | } 2107 | ], 2108 | "courseId": 13, 2109 | "id": 253, 2110 | "name": "完整开源项目", 2111 | "order": 130, 2112 | "parentChapterId": 0, 2113 | "visible": 1 2114 | }, 2115 | { 2116 | "children": [ 2117 | { 2118 | "children": [ 2119 | 2120 | ], 2121 | "courseId": 13, 2122 | "id": 272, 2123 | "name": "常用网站", 2124 | "order": 140000, 2125 | "parentChapterId": 271, 2126 | "visible": 1 2127 | }, 2128 | { 2129 | "children": [ 2130 | 2131 | ], 2132 | "courseId": 13, 2133 | "id": 274, 2134 | "name": "个人博客", 2135 | "order": 140001, 2136 | "parentChapterId": 271, 2137 | "visible": 1 2138 | }, 2139 | { 2140 | "children": [ 2141 | 2142 | ], 2143 | "courseId": 13, 2144 | "id": 281, 2145 | "name": "公司博客", 2146 | "order": 140001, 2147 | "parentChapterId": 271, 2148 | "visible": 1 2149 | }, 2150 | { 2151 | "children": [ 2152 | 2153 | ], 2154 | "courseId": 13, 2155 | "id": 280, 2156 | "name": "开发社区", 2157 | "order": 140001, 2158 | "parentChapterId": 271, 2159 | "visible": 1 2160 | }, 2161 | { 2162 | "children": [ 2163 | 2164 | ], 2165 | "courseId": 13, 2166 | "id": 275, 2167 | "name": "常用工具", 2168 | "order": 140002, 2169 | "parentChapterId": 271, 2170 | "visible": 1 2171 | }, 2172 | { 2173 | "children": [ 2174 | 2175 | ], 2176 | "courseId": 13, 2177 | "id": 276, 2178 | "name": "在线学习", 2179 | "order": 140003, 2180 | "parentChapterId": 271, 2181 | "visible": 1 2182 | }, 2183 | { 2184 | "children": [ 2185 | 2186 | ], 2187 | "courseId": 13, 2188 | "id": 277, 2189 | "name": "开放平台", 2190 | "order": 140004, 2191 | "parentChapterId": 271, 2192 | "visible": 1 2193 | }, 2194 | { 2195 | "children": [ 2196 | 2197 | ], 2198 | "courseId": 13, 2199 | "id": 278, 2200 | "name": "互联网资讯", 2201 | "order": 140005, 2202 | "parentChapterId": 271, 2203 | "visible": 1 2204 | }, 2205 | { 2206 | "children": [ 2207 | 2208 | ], 2209 | "courseId": 13, 2210 | "id": 279, 2211 | "name": "求职招聘", 2212 | "order": 140006, 2213 | "parentChapterId": 271, 2214 | "visible": 1 2215 | }, 2216 | { 2217 | "children": [ 2218 | 2219 | ], 2220 | "courseId": 13, 2221 | "id": 282, 2222 | "name": "应用加固", 2223 | "order": 140007, 2224 | "parentChapterId": 271, 2225 | "visible": 1 2226 | }, 2227 | { 2228 | "children": [ 2229 | 2230 | ], 2231 | "courseId": 13, 2232 | "id": 283, 2233 | "name": "三方支付", 2234 | "order": 140008, 2235 | "parentChapterId": 271, 2236 | "visible": 1 2237 | }, 2238 | { 2239 | "children": [ 2240 | 2241 | ], 2242 | "courseId": 13, 2243 | "id": 284, 2244 | "name": "推送平台", 2245 | "order": 140009, 2246 | "parentChapterId": 271, 2247 | "visible": 1 2248 | }, 2249 | { 2250 | "children": [ 2251 | 2252 | ], 2253 | "courseId": 13, 2254 | "id": 285, 2255 | "name": "三方分享", 2256 | "order": 140010, 2257 | "parentChapterId": 271, 2258 | "visible": 1 2259 | }, 2260 | { 2261 | "children": [ 2262 | 2263 | ], 2264 | "courseId": 13, 2265 | "id": 286, 2266 | "name": "地图平台", 2267 | "order": 140011, 2268 | "parentChapterId": 271, 2269 | "visible": 1 2270 | }, 2271 | { 2272 | "children": [ 2273 | 2274 | ], 2275 | "courseId": 13, 2276 | "id": 287, 2277 | "name": "直播SDK", 2278 | "order": 140012, 2279 | "parentChapterId": 271, 2280 | "visible": 1 2281 | }, 2282 | { 2283 | "children": [ 2284 | 2285 | ], 2286 | "courseId": 13, 2287 | "id": 288, 2288 | "name": "IM即时通讯", 2289 | "order": 140013, 2290 | "parentChapterId": 271, 2291 | "visible": 1 2292 | }, 2293 | { 2294 | "children": [ 2295 | 2296 | ], 2297 | "courseId": 13, 2298 | "id": 289, 2299 | "name": "Bug管理", 2300 | "order": 140014, 2301 | "parentChapterId": 271, 2302 | "visible": 1 2303 | }, 2304 | { 2305 | "children": [ 2306 | 2307 | ], 2308 | "courseId": 13, 2309 | "id": 290, 2310 | "name": "后端云", 2311 | "order": 140015, 2312 | "parentChapterId": 271, 2313 | "visible": 1 2314 | }, 2315 | { 2316 | "children": [ 2317 | 2318 | ], 2319 | "courseId": 13, 2320 | "id": 291, 2321 | "name": "WebView内核", 2322 | "order": 140016, 2323 | "parentChapterId": 271, 2324 | "visible": 1 2325 | }, 2326 | { 2327 | "children": [ 2328 | 2329 | ], 2330 | "courseId": 13, 2331 | "id": 299, 2332 | "name": "创意&素材", 2333 | "order": 140017, 2334 | "parentChapterId": 271, 2335 | "visible": 1 2336 | }, 2337 | { 2338 | "children": [ 2339 | 2340 | ], 2341 | "courseId": 13, 2342 | "id": 300, 2343 | "name": "互联网统计", 2344 | "order": 140018, 2345 | "parentChapterId": 271, 2346 | "visible": 1 2347 | }, 2348 | { 2349 | "children": [ 2350 | 2351 | ], 2352 | "courseId": 13, 2353 | "id": 301, 2354 | "name": "快速开发", 2355 | "order": 140019, 2356 | "parentChapterId": 271, 2357 | "visible": 1 2358 | }, 2359 | { 2360 | "children": [ 2361 | 2362 | ], 2363 | "courseId": 13, 2364 | "id": 359, 2365 | "name": "应用发布", 2366 | "order": 140020, 2367 | "parentChapterId": 271, 2368 | "visible": 1 2369 | }, 2370 | { 2371 | "children": [ 2372 | 2373 | ], 2374 | "courseId": 13, 2375 | "id": 365, 2376 | "name": "反馈平台", 2377 | "order": 140021, 2378 | "parentChapterId": 271, 2379 | "visible": 1 2380 | }, 2381 | { 2382 | "children": [ 2383 | 2384 | ], 2385 | "courseId": 13, 2386 | "id": 366, 2387 | "name": "在线文档", 2388 | "order": 140022, 2389 | "parentChapterId": 271, 2390 | "visible": 1 2391 | }, 2392 | { 2393 | "children": [ 2394 | 2395 | ], 2396 | "courseId": 13, 2397 | "id": 369, 2398 | "name": "短视频SDK", 2399 | "order": 140023, 2400 | "parentChapterId": 271, 2401 | "visible": 1 2402 | } 2403 | ], 2404 | "courseId": 13, 2405 | "id": 271, 2406 | "name": "导航主Tab", 2407 | "order": 140, 2408 | "parentChapterId": 0, 2409 | "visible": 1 2410 | }, 2411 | { 2412 | "children": [ 2413 | { 2414 | "children": [ 2415 | 2416 | ], 2417 | "courseId": 13, 2418 | "id": 294, 2419 | "name": "完整项目", 2420 | "order": 145000, 2421 | "parentChapterId": 293, 2422 | "visible": 0 2423 | }, 2424 | { 2425 | "children": [ 2426 | 2427 | ], 2428 | "courseId": 13, 2429 | "id": 310, 2430 | "name": "下拉刷新", 2431 | "order": 145002, 2432 | "parentChapterId": 293, 2433 | "visible": 1 2434 | }, 2435 | { 2436 | "children": [ 2437 | 2438 | ], 2439 | "courseId": 13, 2440 | "id": 312, 2441 | "name": "富文本编辑器", 2442 | "order": 145003, 2443 | "parentChapterId": 293, 2444 | "visible": 1 2445 | }, 2446 | { 2447 | "children": [ 2448 | 2449 | ], 2450 | "courseId": 13, 2451 | "id": 314, 2452 | "name": "RV列表动效", 2453 | "order": 145004, 2454 | "parentChapterId": 293, 2455 | "visible": 1 2456 | }, 2457 | { 2458 | "children": [ 2459 | 2460 | ], 2461 | "courseId": 13, 2462 | "id": 316, 2463 | "name": "系统源码分析", 2464 | "order": 145005, 2465 | "parentChapterId": 293, 2466 | "visible": 1 2467 | }, 2468 | { 2469 | "children": [ 2470 | 2471 | ], 2472 | "courseId": 13, 2473 | "id": 323, 2474 | "name": "动画", 2475 | "order": 145007, 2476 | "parentChapterId": 293, 2477 | "visible": 1 2478 | }, 2479 | { 2480 | "children": [ 2481 | 2482 | ], 2483 | "courseId": 13, 2484 | "id": 324, 2485 | "name": "组件化", 2486 | "order": 145008, 2487 | "parentChapterId": 293, 2488 | "visible": 1 2489 | }, 2490 | { 2491 | "children": [ 2492 | 2493 | ], 2494 | "courseId": 13, 2495 | "id": 325, 2496 | "name": "PickerView", 2497 | "order": 145009, 2498 | "parentChapterId": 293, 2499 | "visible": 1 2500 | }, 2501 | { 2502 | "children": [ 2503 | 2504 | ], 2505 | "courseId": 13, 2506 | "id": 327, 2507 | "name": "ShapeView", 2508 | "order": 145010, 2509 | "parentChapterId": 293, 2510 | "visible": 1 2511 | }, 2512 | { 2513 | "children": [ 2514 | 2515 | ], 2516 | "courseId": 13, 2517 | "id": 328, 2518 | "name": "文件下载", 2519 | "order": 145011, 2520 | "parentChapterId": 293, 2521 | "visible": 1 2522 | }, 2523 | { 2524 | "children": [ 2525 | 2526 | ], 2527 | "courseId": 13, 2528 | "id": 330, 2529 | "name": "OCR", 2530 | "order": 145012, 2531 | "parentChapterId": 293, 2532 | "visible": 1 2533 | }, 2534 | { 2535 | "children": [ 2536 | 2537 | ], 2538 | "courseId": 13, 2539 | "id": 331, 2540 | "name": "TextView", 2541 | "order": 145013, 2542 | "parentChapterId": 293, 2543 | "visible": 1 2544 | }, 2545 | { 2546 | "children": [ 2547 | 2548 | ], 2549 | "courseId": 13, 2550 | "id": 333, 2551 | "name": "性能优化", 2552 | "order": 145014, 2553 | "parentChapterId": 293, 2554 | "visible": 1 2555 | }, 2556 | { 2557 | "children": [ 2558 | 2559 | ], 2560 | "courseId": 13, 2561 | "id": 336, 2562 | "name": "键盘", 2563 | "order": 145015, 2564 | "parentChapterId": 293, 2565 | "visible": 1 2566 | }, 2567 | { 2568 | "children": [ 2569 | 2570 | ], 2571 | "courseId": 13, 2572 | "id": 337, 2573 | "name": "快应用", 2574 | "order": 145016, 2575 | "parentChapterId": 293, 2576 | "visible": 1 2577 | }, 2578 | { 2579 | "children": [ 2580 | 2581 | ], 2582 | "courseId": 13, 2583 | "id": 338, 2584 | "name": "日历", 2585 | "order": 145017, 2586 | "parentChapterId": 293, 2587 | "visible": 1 2588 | }, 2589 | { 2590 | "children": [ 2591 | 2592 | ], 2593 | "courseId": 13, 2594 | "id": 339, 2595 | "name": "K线图", 2596 | "order": 145018, 2597 | "parentChapterId": 293, 2598 | "visible": 1 2599 | }, 2600 | { 2601 | "children": [ 2602 | 2603 | ], 2604 | "courseId": 13, 2605 | "id": 340, 2606 | "name": "硬件相关", 2607 | "order": 145019, 2608 | "parentChapterId": 293, 2609 | "visible": 1 2610 | }, 2611 | { 2612 | "children": [ 2613 | 2614 | ], 2615 | "courseId": 13, 2616 | "id": 344, 2617 | "name": "Fragment", 2618 | "order": 145020, 2619 | "parentChapterId": 293, 2620 | "visible": 1 2621 | }, 2622 | { 2623 | "children": [ 2624 | 2625 | ], 2626 | "courseId": 13, 2627 | "id": 347, 2628 | "name": "图层引导", 2629 | "order": 145021, 2630 | "parentChapterId": 293, 2631 | "visible": 1 2632 | }, 2633 | { 2634 | "children": [ 2635 | 2636 | ], 2637 | "courseId": 13, 2638 | "id": 357, 2639 | "name": "表格类", 2640 | "order": 145022, 2641 | "parentChapterId": 293, 2642 | "visible": 1 2643 | }, 2644 | { 2645 | "children": [ 2646 | 2647 | ], 2648 | "courseId": 13, 2649 | "id": 358, 2650 | "name": "项目基础功能", 2651 | "order": 145023, 2652 | "parentChapterId": 293, 2653 | "visible": 1 2654 | }, 2655 | { 2656 | "children": [ 2657 | 2658 | ], 2659 | "courseId": 13, 2660 | "id": 363, 2661 | "name": "创意汇", 2662 | "order": 145024, 2663 | "parentChapterId": 293, 2664 | "visible": 1 2665 | }, 2666 | { 2667 | "children": [ 2668 | 2669 | ], 2670 | "courseId": 13, 2671 | "id": 367, 2672 | "name": "资源聚合类", 2673 | "order": 145025, 2674 | "parentChapterId": 293, 2675 | "visible": 1 2676 | }, 2677 | { 2678 | "children": [ 2679 | 2680 | ], 2681 | "courseId": 13, 2682 | "id": 370, 2683 | "name": "微信小程序", 2684 | "order": 145026, 2685 | "parentChapterId": 293, 2686 | "visible": 1 2687 | }, 2688 | { 2689 | "children": [ 2690 | 2691 | ], 2692 | "courseId": 13, 2693 | "id": 371, 2694 | "name": "Flutter项目", 2695 | "order": 145027, 2696 | "parentChapterId": 293, 2697 | "visible": 1 2698 | }, 2699 | { 2700 | "children": [ 2701 | 2702 | ], 2703 | "courseId": 13, 2704 | "id": 378, 2705 | "name": "CounterView", 2706 | "order": 145028, 2707 | "parentChapterId": 293, 2708 | "visible": 1 2709 | }, 2710 | { 2711 | "children": [ 2712 | 2713 | ], 2714 | "courseId": 13, 2715 | "id": 380, 2716 | "name": "ImageView", 2717 | "order": 145029, 2718 | "parentChapterId": 293, 2719 | "visible": 1 2720 | } 2721 | ], 2722 | "courseId": 13, 2723 | "id": 293, 2724 | "name": "开源项目主Tab", 2725 | "order": 145, 2726 | "parentChapterId": 0, 2727 | "visible": 0 2728 | }, 2729 | { 2730 | "children": [ 2731 | { 2732 | "children": [ 2733 | 2734 | ], 2735 | "courseId": 13, 2736 | "id": 298, 2737 | "name": "我的博客", 2738 | "order": 150000, 2739 | "parentChapterId": 297, 2740 | "visible": 1 2741 | }, 2742 | { 2743 | "children": [ 2744 | 2745 | ], 2746 | "courseId": 13, 2747 | "id": 360, 2748 | "name": "小编发布", 2749 | "order": 150001, 2750 | "parentChapterId": 297, 2751 | "visible": 1 2752 | } 2753 | ], 2754 | "courseId": 13, 2755 | "id": 297, 2756 | "name": "原创文章", 2757 | "order": 150, 2758 | "parentChapterId": 0, 2759 | "visible": 1 2760 | }, 2761 | { 2762 | "children": [ 2763 | { 2764 | "children": [ 2765 | 2766 | ], 2767 | "courseId": 13, 2768 | "id": 343, 2769 | "name": "TV", 2770 | "order": 155000, 2771 | "parentChapterId": 342, 2772 | "visible": 1 2773 | } 2774 | ], 2775 | "courseId": 13, 2776 | "id": 342, 2777 | "name": "TV相关", 2778 | "order": 155, 2779 | "parentChapterId": 0, 2780 | "visible": 1 2781 | }, 2782 | { 2783 | "children": [ 2784 | { 2785 | "children": [ 2786 | 2787 | ], 2788 | "courseId": 13, 2789 | "id": 349, 2790 | "name": "日历", 2791 | "order": 160000, 2792 | "parentChapterId": 348, 2793 | "visible": 1 2794 | }, 2795 | { 2796 | "children": [ 2797 | 2798 | ], 2799 | "courseId": 13, 2800 | "id": 350, 2801 | "name": "电影&音乐", 2802 | "order": 160001, 2803 | "parentChapterId": 348, 2804 | "visible": 1 2805 | }, 2806 | { 2807 | "children": [ 2808 | 2809 | ], 2810 | "courseId": 13, 2811 | "id": 352, 2812 | "name": "资讯", 2813 | "order": 160002, 2814 | "parentChapterId": 348, 2815 | "visible": 1 2816 | }, 2817 | { 2818 | "children": [ 2819 | 2820 | ], 2821 | "courseId": 13, 2822 | "id": 353, 2823 | "name": "快递", 2824 | "order": 160003, 2825 | "parentChapterId": 348, 2826 | "visible": 1 2827 | }, 2828 | { 2829 | "children": [ 2830 | 2831 | ], 2832 | "courseId": 13, 2833 | "id": 354, 2834 | "name": "天气", 2835 | "order": 160004, 2836 | "parentChapterId": 348, 2837 | "visible": 1 2838 | }, 2839 | { 2840 | "children": [ 2841 | 2842 | ], 2843 | "courseId": 13, 2844 | "id": 355, 2845 | "name": "他人收集", 2846 | "order": 160005, 2847 | "parentChapterId": 348, 2848 | "visible": 1 2849 | }, 2850 | { 2851 | "children": [ 2852 | 2853 | ], 2854 | "courseId": 13, 2855 | "id": 356, 2856 | "name": "翻译", 2857 | "order": 160006, 2858 | "parentChapterId": 348, 2859 | "visible": 1 2860 | } 2861 | ], 2862 | "courseId": 13, 2863 | "id": 348, 2864 | "name": "开放API", 2865 | "order": 160, 2866 | "parentChapterId": 0, 2867 | "visible": 1 2868 | }, 2869 | { 2870 | "children": [ 2871 | { 2872 | "children": [ 2873 | 2874 | ], 2875 | "courseId": 13, 2876 | "id": 375, 2877 | "name": "Flutter", 2878 | "order": 165000, 2879 | "parentChapterId": 374, 2880 | "visible": 1 2881 | } 2882 | ], 2883 | "courseId": 13, 2884 | "id": 374, 2885 | "name": "Flutter", 2886 | "order": 165, 2887 | "parentChapterId": 0, 2888 | "visible": 1 2889 | }, 2890 | { 2891 | "children": [ 2892 | { 2893 | "children": [ 2894 | 2895 | ], 2896 | "courseId": 13, 2897 | "id": 377, 2898 | "name": "优质内推", 2899 | "order": 170000, 2900 | "parentChapterId": 376, 2901 | "visible": 1 2902 | } 2903 | ], 2904 | "courseId": 13, 2905 | "id": 376, 2906 | "name": "内推", 2907 | "order": 170, 2908 | "parentChapterId": 0, 2909 | "visible": 1 2910 | } 2911 | ], 2912 | "errorCode": 0, 2913 | "errorMsg": "" 2914 | } -------------------------------------------------------------------------------- /Test/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | _fe_analyzer_shared: 5 | dependency: transitive 6 | description: 7 | name: _fe_analyzer_shared 8 | url: "https://pub.flutter-io.cn" 9 | source: hosted 10 | version: "6.0.0" 11 | analyzer: 12 | dependency: transitive 13 | description: 14 | name: analyzer 15 | url: "https://pub.flutter-io.cn" 16 | source: hosted 17 | version: "0.39.15" 18 | args: 19 | dependency: transitive 20 | description: 21 | name: args 22 | url: "https://pub.flutter-io.cn" 23 | source: hosted 24 | version: "1.6.0" 25 | async: 26 | dependency: transitive 27 | description: 28 | name: async 29 | url: "https://pub.flutter-io.cn" 30 | source: hosted 31 | version: "2.4.2" 32 | boolean_selector: 33 | dependency: transitive 34 | description: 35 | name: boolean_selector 36 | url: "https://pub.flutter-io.cn" 37 | source: hosted 38 | version: "2.0.0" 39 | charcode: 40 | dependency: transitive 41 | description: 42 | name: charcode 43 | url: "https://pub.flutter-io.cn" 44 | source: hosted 45 | version: "1.1.3" 46 | cli_util: 47 | dependency: transitive 48 | description: 49 | name: cli_util 50 | url: "https://pub.flutter-io.cn" 51 | source: hosted 52 | version: "0.1.4" 53 | collection: 54 | dependency: transitive 55 | description: 56 | name: collection 57 | url: "https://pub.flutter-io.cn" 58 | source: hosted 59 | version: "1.14.13" 60 | convert: 61 | dependency: transitive 62 | description: 63 | name: convert 64 | url: "https://pub.flutter-io.cn" 65 | source: hosted 66 | version: "2.1.1" 67 | coverage: 68 | dependency: transitive 69 | description: 70 | name: coverage 71 | url: "https://pub.flutter-io.cn" 72 | source: hosted 73 | version: "0.14.0" 74 | crypto: 75 | dependency: transitive 76 | description: 77 | name: crypto 78 | url: "https://pub.flutter-io.cn" 79 | source: hosted 80 | version: "2.1.5" 81 | csslib: 82 | dependency: transitive 83 | description: 84 | name: csslib 85 | url: "https://pub.flutter-io.cn" 86 | source: hosted 87 | version: "0.16.2" 88 | glob: 89 | dependency: transitive 90 | description: 91 | name: glob 92 | url: "https://pub.flutter-io.cn" 93 | source: hosted 94 | version: "1.2.0" 95 | html: 96 | dependency: transitive 97 | description: 98 | name: html 99 | url: "https://pub.flutter-io.cn" 100 | source: hosted 101 | version: "0.14.0+3" 102 | http: 103 | dependency: transitive 104 | description: 105 | name: http 106 | url: "https://pub.flutter-io.cn" 107 | source: hosted 108 | version: "0.12.2" 109 | http_multi_server: 110 | dependency: transitive 111 | description: 112 | name: http_multi_server 113 | url: "https://pub.flutter-io.cn" 114 | source: hosted 115 | version: "2.0.5" 116 | http_parser: 117 | dependency: transitive 118 | description: 119 | name: http_parser 120 | url: "https://pub.flutter-io.cn" 121 | source: hosted 122 | version: "3.1.4" 123 | io: 124 | dependency: transitive 125 | description: 126 | name: io 127 | url: "https://pub.flutter-io.cn" 128 | source: hosted 129 | version: "0.3.4" 130 | js: 131 | dependency: transitive 132 | description: 133 | name: js 134 | url: "https://pub.flutter-io.cn" 135 | source: hosted 136 | version: "0.6.2" 137 | logging: 138 | dependency: transitive 139 | description: 140 | name: logging 141 | url: "https://pub.flutter-io.cn" 142 | source: hosted 143 | version: "0.11.4" 144 | matcher: 145 | dependency: transitive 146 | description: 147 | name: matcher 148 | url: "https://pub.flutter-io.cn" 149 | source: hosted 150 | version: "0.12.9" 151 | meta: 152 | dependency: transitive 153 | description: 154 | name: meta 155 | url: "https://pub.flutter-io.cn" 156 | source: hosted 157 | version: "1.2.2" 158 | mime: 159 | dependency: transitive 160 | description: 161 | name: mime 162 | url: "https://pub.flutter-io.cn" 163 | source: hosted 164 | version: "0.9.6+3" 165 | node_interop: 166 | dependency: transitive 167 | description: 168 | name: node_interop 169 | url: "https://pub.flutter-io.cn" 170 | source: hosted 171 | version: "1.1.1" 172 | node_io: 173 | dependency: transitive 174 | description: 175 | name: node_io 176 | url: "https://pub.flutter-io.cn" 177 | source: hosted 178 | version: "1.1.1" 179 | node_preamble: 180 | dependency: transitive 181 | description: 182 | name: node_preamble 183 | url: "https://pub.flutter-io.cn" 184 | source: hosted 185 | version: "1.4.12" 186 | package_config: 187 | dependency: transitive 188 | description: 189 | name: package_config 190 | url: "https://pub.flutter-io.cn" 191 | source: hosted 192 | version: "1.9.3" 193 | path: 194 | dependency: transitive 195 | description: 196 | name: path 197 | url: "https://pub.flutter-io.cn" 198 | source: hosted 199 | version: "1.7.0" 200 | pedantic: 201 | dependency: transitive 202 | description: 203 | name: pedantic 204 | url: "https://pub.flutter-io.cn" 205 | source: hosted 206 | version: "1.9.2" 207 | pool: 208 | dependency: transitive 209 | description: 210 | name: pool 211 | url: "https://pub.flutter-io.cn" 212 | source: hosted 213 | version: "1.4.0" 214 | pub_semver: 215 | dependency: transitive 216 | description: 217 | name: pub_semver 218 | url: "https://pub.flutter-io.cn" 219 | source: hosted 220 | version: "1.4.4" 221 | shelf: 222 | dependency: transitive 223 | description: 224 | name: shelf 225 | url: "https://pub.flutter-io.cn" 226 | source: hosted 227 | version: "0.7.7" 228 | shelf_packages_handler: 229 | dependency: transitive 230 | description: 231 | name: shelf_packages_handler 232 | url: "https://pub.flutter-io.cn" 233 | source: hosted 234 | version: "2.0.0" 235 | shelf_static: 236 | dependency: transitive 237 | description: 238 | name: shelf_static 239 | url: "https://pub.flutter-io.cn" 240 | source: hosted 241 | version: "0.2.8" 242 | shelf_web_socket: 243 | dependency: transitive 244 | description: 245 | name: shelf_web_socket 246 | url: "https://pub.flutter-io.cn" 247 | source: hosted 248 | version: "0.2.3" 249 | source_map_stack_trace: 250 | dependency: transitive 251 | description: 252 | name: source_map_stack_trace 253 | url: "https://pub.flutter-io.cn" 254 | source: hosted 255 | version: "2.0.0" 256 | source_maps: 257 | dependency: transitive 258 | description: 259 | name: source_maps 260 | url: "https://pub.flutter-io.cn" 261 | source: hosted 262 | version: "0.10.9" 263 | source_span: 264 | dependency: transitive 265 | description: 266 | name: source_span 267 | url: "https://pub.flutter-io.cn" 268 | source: hosted 269 | version: "1.7.0" 270 | stack_trace: 271 | dependency: transitive 272 | description: 273 | name: stack_trace 274 | url: "https://pub.flutter-io.cn" 275 | source: hosted 276 | version: "1.9.5" 277 | stream_channel: 278 | dependency: transitive 279 | description: 280 | name: stream_channel 281 | url: "https://pub.flutter-io.cn" 282 | source: hosted 283 | version: "2.0.0" 284 | string_scanner: 285 | dependency: transitive 286 | description: 287 | name: string_scanner 288 | url: "https://pub.flutter-io.cn" 289 | source: hosted 290 | version: "1.0.5" 291 | term_glyph: 292 | dependency: transitive 293 | description: 294 | name: term_glyph 295 | url: "https://pub.flutter-io.cn" 296 | source: hosted 297 | version: "1.1.0" 298 | test: 299 | dependency: "direct main" 300 | description: 301 | name: test 302 | url: "https://pub.flutter-io.cn" 303 | source: hosted 304 | version: "1.15.3" 305 | test_api: 306 | dependency: transitive 307 | description: 308 | name: test_api 309 | url: "https://pub.flutter-io.cn" 310 | source: hosted 311 | version: "0.2.18" 312 | test_core: 313 | dependency: transitive 314 | description: 315 | name: test_core 316 | url: "https://pub.flutter-io.cn" 317 | source: hosted 318 | version: "0.3.11" 319 | typed_data: 320 | dependency: transitive 321 | description: 322 | name: typed_data 323 | url: "https://pub.flutter-io.cn" 324 | source: hosted 325 | version: "1.2.0" 326 | vm_service: 327 | dependency: transitive 328 | description: 329 | name: vm_service 330 | url: "https://pub.flutter-io.cn" 331 | source: hosted 332 | version: "4.1.0" 333 | watcher: 334 | dependency: transitive 335 | description: 336 | name: watcher 337 | url: "https://pub.flutter-io.cn" 338 | source: hosted 339 | version: "0.9.7+15" 340 | web_socket_channel: 341 | dependency: transitive 342 | description: 343 | name: web_socket_channel 344 | url: "https://pub.flutter-io.cn" 345 | source: hosted 346 | version: "1.1.0" 347 | webkit_inspection_protocol: 348 | dependency: transitive 349 | description: 350 | name: webkit_inspection_protocol 351 | url: "https://pub.flutter-io.cn" 352 | source: hosted 353 | version: "0.7.3" 354 | yaml: 355 | dependency: transitive 356 | description: 357 | name: yaml 358 | url: "https://pub.flutter-io.cn" 359 | source: hosted 360 | version: "2.2.1" 361 | sdks: 362 | dart: ">=2.7.0 <3.0.0" 363 | -------------------------------------------------------------------------------- /Test/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: Test 2 | description: Test 3 | 4 | environment: 5 | sdk: '>=2.12.0 <3.0.0' 6 | 7 | dev_dependencies: 8 | test: ^1.16.5 9 | -------------------------------------------------------------------------------- /Test/test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'dart:math' show min; 3 | import 'dart:convert' show json; 4 | import 'package:test/test.dart'; 5 | import 'EmptyResp.dart'; 6 | import 'RegionResp.dart'; 7 | import 'WanResp.dart'; 8 | import 'ListsResp.dart'; 9 | import 'IgnoreMapResp.dart'; 10 | import 'ListTopResp.dart'; 11 | import 'ListWithStringResp.dart'; 12 | 13 | void main() { 14 | test('Test null', () { 15 | String str0 = '{"asd":[[[1]]],"qwe":[[[{"zxc":1}]]],"qaz":[1]}'; 16 | String str1 = 17 | '{"asd":[[[null,null]]],"qwe":[[[null]]],"qaz":[null,null,null]}'; 18 | String str2 = '{"asd":[[[]]],"qwe":[[[{"zxc":null}]]],"qaz":[null]}'; 19 | String str3 = '{"asd":[[]],"qwe":[[[]]],"qaz":null}'; 20 | String str4 = '{"asd":[],"qwe":[[]],"qaz":null}'; 21 | String str5 = '{"asd":null,"qwe":[],"qaz":null}'; 22 | String? str6; 23 | expect(json.decode(new ListsResp(str0).toString()), json.decode(str0)); 24 | expect(json.decode(new ListsResp(str1).toString()), json.decode(str1)); 25 | expect(json.decode(new ListsResp(str2).toString()), json.decode(str2)); 26 | expect(json.decode(new ListsResp(str3).toString()), json.decode(str3)); 27 | expect(json.decode(new ListsResp(str4).toString()), json.decode(str4)); 28 | expect(json.decode(new ListsResp(str5).toString()), json.decode(str5)); 29 | expect(ListsResp.parse(str6), null); 30 | }); 31 | test('Test Region', () { 32 | String str = readFromFile('Region'); 33 | RegionResp resp = new RegionResp(str); 34 | var jsonRes = json.decode(str); 35 | 36 | /// 测试传入String和json进行解析的结果是否相同 37 | expect(resp.toString(), new RegionResp(jsonRes).toString()); 38 | 39 | /// 逐个字段检查是否与json.decode()的结果相同 40 | expect(resp.code, jsonRes['code']); 41 | expect(resp.message, jsonRes['message']); 42 | expect(resp.ttl, jsonRes['ttl']); 43 | expect(resp.data!.page!.num, jsonRes['data']['page']['num']); 44 | expect(resp.data!.page!.size, jsonRes['data']['page']['size']); 45 | expect(resp.data!.page!.count, jsonRes['data']['page']['count']); 46 | for (var index = 0; index < jsonRes['data']['archives'].length; index++) { 47 | expect(resp.data!.archives![index]!.aid, 48 | jsonRes['data']['archives'][index]['aid']); 49 | expect(resp.data!.archives![index]!.attribute, 50 | jsonRes['data']['archives'][index]['attribute']); 51 | expect(resp.data!.archives![index]!.copyright, 52 | jsonRes['data']['archives'][index]['copyright']); 53 | expect(resp.data!.archives![index]!.ctime, 54 | jsonRes['data']['archives'][index]['ctime']); 55 | expect(resp.data!.archives![index]!.desc, 56 | jsonRes['data']['archives'][index]['desc']); 57 | expect(resp.data!.archives![index]!.duration, 58 | jsonRes['data']['archives'][index]['duration']); 59 | expect(resp.data!.archives![index]!.dynamic, 60 | jsonRes['data']['archives'][index]['dynamic']); 61 | expect(resp.data!.archives![index]!.pic, 62 | jsonRes['data']['archives'][index]['pic']); 63 | expect(resp.data!.archives![index]!.pubdate, 64 | jsonRes['data']['archives'][index]['pubdate']); 65 | expect(resp.data!.archives![index]!.state, 66 | jsonRes['data']['archives'][index]['state']); 67 | expect(resp.data!.archives![index]!.tid, 68 | jsonRes['data']['archives'][index]['tid']); 69 | expect(resp.data!.archives![index]!.title, 70 | jsonRes['data']['archives'][index]['title']); 71 | expect(resp.data!.archives![index]!.tname, 72 | jsonRes['data']['archives'][index]['tname']); 73 | expect(resp.data!.archives![index]!.videos, 74 | jsonRes['data']['archives'][index]['videos']); 75 | 76 | expect(resp.data!.archives![index]!.rights!.bp, 77 | jsonRes['data']['archives'][index]['rights']['bp']); 78 | expect(resp.data!.archives![index]!.rights!.download, 79 | jsonRes['data']['archives'][index]['rights']['download']); 80 | expect(resp.data!.archives![index]!.rights!.elec, 81 | jsonRes['data']['archives'][index]['rights']['elec']); 82 | expect(resp.data!.archives![index]!.rights!.hd5, 83 | jsonRes['data']['archives'][index]['rights']['hd5']); 84 | expect(resp.data!.archives![index]!.rights!.movie, 85 | jsonRes['data']['archives'][index]['rights']['movie']); 86 | expect(resp.data!.archives![index]!.rights!.no_reprint, 87 | jsonRes['data']['archives'][index]['rights']['no_reprint']); 88 | expect(resp.data!.archives![index]!.rights!.pay, 89 | jsonRes['data']['archives'][index]['rights']['pay']); 90 | 91 | expect(resp.data!.archives![index]!.owner!.face, 92 | jsonRes['data']['archives'][index]['owner']['face']); 93 | expect(resp.data!.archives![index]!.owner!.mid, 94 | jsonRes['data']['archives'][index]['owner']['mid']); 95 | expect(resp.data!.archives![index]!.owner!.name, 96 | jsonRes['data']['archives'][index]['owner']['name']); 97 | 98 | expect(resp.data!.archives![index]!.stat!.aid, 99 | jsonRes['data']['archives'][index]['stat']['aid']); 100 | expect(resp.data!.archives![index]!.stat!.coin, 101 | jsonRes['data']['archives'][index]['stat']['coin']); 102 | expect(resp.data!.archives![index]!.stat!.danmaku, 103 | jsonRes['data']['archives'][index]['stat']['danmaku']); 104 | expect(resp.data!.archives![index]!.stat!.favorite, 105 | jsonRes['data']['archives'][index]['stat']['favorite']); 106 | expect(resp.data!.archives![index]!.stat!.his_rank, 107 | jsonRes['data']['archives'][index]['stat']['his_rank']); 108 | expect(resp.data!.archives![index]!.stat!.like, 109 | jsonRes['data']['archives'][index]['stat']['like']); 110 | expect(resp.data!.archives![index]!.stat!.now_rank, 111 | jsonRes['data']['archives'][index]['stat']['now_rank']); 112 | expect(resp.data!.archives![index]!.stat!.reply, 113 | jsonRes['data']['archives'][index]['stat']['reply']); 114 | expect(resp.data!.archives![index]!.stat!.share, 115 | jsonRes['data']['archives'][index]['stat']['share']); 116 | expect(resp.data!.archives![index]!.stat!.view, 117 | jsonRes['data']['archives'][index]['stat']['view']); 118 | } 119 | 120 | /// 检查bean对象toString还原成json字符串后再交给json解析的结果是否与原始字符串相同 121 | expect(jsonRes, json.decode(resp.toString())); 122 | }); 123 | 124 | test('Test WanAndroid', () { 125 | String str = readFromFile('Wan'); 126 | WanResp resp = new WanResp(str); 127 | var jsonRes = json.decode(str); 128 | 129 | /// 测试传入String和json进行解析的结果是否相同 130 | expect(resp.toString(), new WanResp(jsonRes).toString()); 131 | 132 | /// 逐个字段检查是否与json.decode()的结果相同 133 | expect(resp.errorCode, jsonRes['errorCode']); 134 | expect(resp.errorMsg, jsonRes['errorMsg']); 135 | 136 | for (var index = 0; index < jsonRes['data'].length; index++) { 137 | expect(resp.data![index]!.name, jsonRes['data'][index]['name']); 138 | expect(resp.data![index]!.courseId, jsonRes['data'][index]['courseId']); 139 | expect(resp.data![index]!.id, jsonRes['data'][index]['id']); 140 | expect(resp.data![index]!.order, jsonRes['data'][index]['order']); 141 | expect(resp.data![index]!.parentChapterId, 142 | jsonRes['data'][index]['parentChapterId']); 143 | expect(resp.data![index]!.visible, jsonRes['data'][index]['visible']); 144 | 145 | for (var cindex = 0; 146 | cindex < jsonRes['data'][index]['children'].length; 147 | cindex++) { 148 | expect(resp.data![index]!.children![cindex]!.children, 149 | jsonRes['data'][index]['children'][cindex]['children']); 150 | expect(resp.data![index]!.children![cindex]!.visible, 151 | jsonRes['data'][index]['children'][cindex]['visible']); 152 | expect(resp.data![index]!.children![cindex]!.parentChapterId, 153 | jsonRes['data'][index]['children'][cindex]['parentChapterId']); 154 | expect(resp.data![index]!.children![cindex]!.order, 155 | jsonRes['data'][index]['children'][cindex]['order']); 156 | expect(resp.data![index]!.children![cindex]!.courseId, 157 | jsonRes['data'][index]['children'][cindex]['courseId']); 158 | expect(resp.data![index]!.children![cindex]!.name, 159 | jsonRes['data'][index]['children'][cindex]['name']); 160 | expect(resp.data![index]!.children![cindex]!.id, 161 | jsonRes['data'][index]['children'][cindex]['id']); 162 | } 163 | 164 | /// 检查bean对象toString还原成json字符串后再交给json解析的结果是否与原始字符串相同 165 | expect(jsonRes, json.decode(resp.toString())); 166 | } 167 | }); 168 | 169 | test('test many empty', () { 170 | String str = readFromFile('Empty'); 171 | EmptyResp resp = new EmptyResp(str); 172 | var jsonRes = json.decode(str); 173 | 174 | /// 测试传入String和json进行解析的结果是否相同 175 | expect(resp.toString(), new EmptyResp(jsonRes).toString()); 176 | 177 | /// 逐个字段检查是否与json.decode()的结果相同 178 | expect(resp.qwe!.asd, jsonRes['qwe']['asd']); 179 | expect(resp.qwe!.zxc, jsonRes['qwe']['zxc']); 180 | expect(resp.qwe!.qaz, jsonRes['qwe']['qaz']); 181 | 182 | /// 检查bean对象toString还原成json字符串后再交给json解析的结果是否与原始字符串相同 183 | expect(jsonRes, json.decode(resp.toString())); 184 | }); 185 | 186 | test('test fucking lists', () { 187 | String str = readFromFile('Lists'); 188 | ListsResp resp = new ListsResp(str); 189 | var jsonRes = json.decode(str); 190 | 191 | /// 测试传入String和json进行解析的结果是否相同 192 | expect(resp.toString(), new ListsResp(jsonRes).toString()); 193 | 194 | /// 逐个字段检查是否与json.decode()的结果相同 195 | expect(resp.asd![0]![0]![0], jsonRes['asd'][0][0][0]); 196 | expect(resp.qwe![0]![0]![0]!.zxc, jsonRes['qwe'][0][0][0]['zxc']); 197 | expect(resp.qaz, jsonRes['qaz']); 198 | 199 | /// 检查bean对象toString还原成json字符串后再交给json解析的结果是否与原始字符串相同 200 | expect(jsonRes, json.decode(resp.toString())); 201 | }); 202 | 203 | test('test ignore map', () { 204 | String str = readFromFile('IgnoreMap'); 205 | IgnoreMapResp resp = new IgnoreMapResp(str); 206 | var jsonRes = json.decode(str); 207 | 208 | /// 测试传入String和json进行解析的结果是否相同 209 | expect(resp.toString(), new IgnoreMapResp(jsonRes).toString()); 210 | 211 | /// 逐个字段检查是否与json.decode()的结果相同 212 | expect(resp.data!.wc, jsonRes['data']['wc']); 213 | expect(resp.data!.author, jsonRes['data']['author']); 214 | expect(resp.data!.content, jsonRes['data']['content']); 215 | expect(resp.data!.digest, jsonRes['data']['digest']); 216 | expect(resp.data!.title, jsonRes['data']['title']); 217 | expect(resp.data!.extra!.a, jsonRes['data']['extra']['a']); 218 | expect(resp.data!.extra!.b, jsonRes['data']['extra']['b']); 219 | expect(resp.data!.extra!.c, jsonRes['data']['extra']['c']); 220 | expect(resp.data!.date, jsonRes['data']['date']); 221 | 222 | /// 检查bean对象toString还原成json字符串后再交给json解析的结果是否与原始字符串相同 223 | expect(jsonRes, json.decode(resp.toString())); 224 | }); 225 | 226 | test('test list top json', () { 227 | String str = readFromFile('ListTop'); 228 | ListTopResp resp = ListTopResp(str); 229 | 230 | var jsonRes = json.decode(str); 231 | 232 | /// 测试传入String和json进行解析的结果是否相同 233 | expect(resp.toString(), new ListTopResp(jsonRes).toString()); 234 | expect(resp.list.toString().replaceAll(' ', ''), 235 | json.encode(jsonRes).replaceAll('\n', '').replaceAll(' ', '')); 236 | }); 237 | 238 | test('test list with string', () { 239 | String str = readFromFile('ListWithString'); 240 | ListWithStringResp resp = ListWithStringResp(str); 241 | 242 | var jsonRes = json.decode(str); 243 | 244 | /// 测试传入String和json进行解析的结果是否相同 245 | expect(resp.toString(), new ListWithStringResp(jsonRes).toString()); 246 | expect(resp.toString().replaceAll(' ', ''), 247 | json.encode(jsonRes).replaceAll('\n', '').replaceAll(' ', '')); 248 | }); 249 | } 250 | 251 | String readFromFile(String name) { 252 | String str = new File('${name}Test.json').readAsStringSync(); 253 | int index1 = str.indexOf('{'); 254 | int index2 = str.indexOf('['); 255 | index1 = index1 == -1 ? 0 : index1; 256 | index2 = index2 == -1 ? 0 : index2; 257 | return str.substring(min(index1, index2)); 258 | } 259 | -------------------------------------------------------------------------------- /Test/test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # 下载依赖 4 | env PUB_ALLOW_PRERELEASE_SDK=quiet $(echo $(which flutter) | awk '{print substr($0,0,length()-7)}')cache/dart-sdk/bin/pub get 5 | 6 | # 运行测试 7 | echo test start... 8 | $(echo $(which flutter) | awk '{print substr($0,0,length()-7)}')cache/dart-sdk/bin/dart test.dart 9 | -------------------------------------------------------------------------------- /check_version.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding:utf-8 -*- 3 | # @Filename : check_version.py 4 | # @Date : 18-8-20 上午1:52 5 | # @Author : DebuggerX 6 | 7 | import configparser 8 | import os 9 | import ssl 10 | import sys 11 | from urllib import request 12 | from json import loads 13 | 14 | from PyQt5 import QtGui, QtCore 15 | from PyQt5.QtCore import QThread, pyqtSignal 16 | from PyQt5.QtWidgets import QMessageBox 17 | from tools import msg_box_ui 18 | 19 | code = 0.9 20 | ignore_code = 0.0 21 | 22 | check_last_version_thread = None 23 | 24 | 25 | def get_exe_path(): 26 | if getattr(sys, 'frozen', False): 27 | return os.path.dirname(sys.executable) 28 | else: 29 | return os.path.dirname(__file__) 30 | 31 | 32 | def _check_ignore_version(): 33 | config = configparser.ConfigParser() 34 | global ignore_code 35 | # noinspection PyBroadException 36 | try: 37 | config.read(os.path.join(get_exe_path(), '.ignore.cfg')) 38 | ignore_code = float(config.get('version', 'code')) 39 | except Exception: 40 | pass 41 | 42 | 43 | class CheckLastVersion(QThread): 44 | trigger = pyqtSignal(dict) 45 | 46 | def run(self): 47 | res_json = None 48 | # noinspection PyBroadException 49 | try: 50 | res = request.urlopen('https://raw.githubusercontent.com/debuggerx01/JSONFormat4Flutter/master/version', 51 | context=ssl._create_unverified_context()) 52 | res_json = loads(res.read().decode()) 53 | except Exception: 54 | pass 55 | if res_json is not None: 56 | global code 57 | if res_json['code'] > code and res_json['code'] > ignore_code: 58 | self.trigger.emit(res_json) 59 | 60 | 61 | def check_last_version_handler(json_obj): 62 | msg_box = QMessageBox() 63 | msg_box.addButton('确定', QMessageBox.AcceptRole) 64 | msg_box.addButton('忽略', QMessageBox.NoRole) 65 | msg_box.addButton('关闭', QMessageBox.RejectRole) 66 | msg_box.setParent(msg_box_ui) 67 | msg_box.setWindowTitle("有新版本更新!") 68 | msg_box.setText("新版本(v%s)更新内容:\n%s\n\n点击[确定]转跳到下载页,点击[忽略]忽略该版本提醒,点击[关闭]退出本提示框" % (json_obj['code'], json_obj['desc'])) 69 | 70 | res = msg_box.exec() 71 | if res == QMessageBox.RejectRole: 72 | config = configparser.ConfigParser() 73 | config.add_section('version') 74 | config.set('version', 'code', str(json_obj['code'])) 75 | with open(os.path.join(get_exe_path(), '.ignore.cfg'), 'w') as configfile: 76 | config.write(configfile) 77 | elif res == QMessageBox.AcceptRole: 78 | QtGui.QDesktopServices.openUrl(QtCore.QUrl('https://github.com/debuggerx01/JSONFormat4Flutter/releases')) 79 | 80 | 81 | def check_version(): 82 | _check_ignore_version() 83 | 84 | global check_last_version_thread 85 | check_last_version_thread = CheckLastVersion() 86 | check_last_version_thread.trigger.connect(check_last_version_handler) 87 | check_last_version_thread.start() 88 | return code 89 | -------------------------------------------------------------------------------- /formatter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding:utf-8 -*- 3 | # @Filename : formatter.py 4 | # @Date : 18-1-26 下午5:48 5 | # @Author : DebuggerX 6 | from functools import partial 7 | 8 | from mainwindow import * 9 | from tools import * 10 | from check_version import * 11 | 12 | # 第三方库 用于解决跨平台的复制粘贴和复制的文本带bom问题 13 | import pyperclip 14 | 15 | # 定义显示的json格式化字符串的缩进量为4个空格 16 | indent = ' ' * 4 17 | 18 | # 临时存储 19 | last_list_com_box = None 20 | 21 | 22 | def analyse_json_obj(json_obj, level=0, res=None, json_key=None): 23 | if res is None: 24 | res = [] 25 | 26 | # 对字典对象的属性按照基本数据类型->list类型->dict类型的次序排序 27 | if isinstance(json_obj, dict): 28 | json_obj = sorted( 29 | json_obj.items(), key=lambda x: 30 | ( 31 | isinstance(x[1], dict), 32 | isinstance(x[1], list), 33 | isinstance(x[1], str), 34 | isinstance(x[1], bool), 35 | isinstance(x[1], float), 36 | isinstance(x[1], int), 37 | x[0] 38 | ) 39 | ) 40 | 41 | # 插入父层级数据为dict类型,类型序号为8,层级深度+1,遍历解析所有子属性 42 | if json_key is None: 43 | res.append('%s<[dict]>8' % (indent * level)) 44 | else: 45 | res.append('%s<%s> : <[dict]>8' % (indent * level, json_key)) 46 | level += 1 47 | for key, value in json_obj: 48 | analyse_json_obj(value, level, res, key) 49 | 50 | elif isinstance(json_obj, list): 51 | # 插入父层级数据为list类型,类型序号为9,层级深度+1,并取其第一个元素继续解析 52 | if json_key is None: 53 | res.append('%s<[list]>9' % (indent * level)) 54 | else: 55 | res.append('%s<%s> : <[list]>9' % (indent * level, json_key)) 56 | if len(json_obj) > 0: 57 | json_obj_temp = json_obj[0] 58 | if len(list(filter(lambda item: type(item) is dict or item is None, json_obj))) == len(json_obj): 59 | for i in range(1, len(json_obj)): 60 | for k in json_obj[i] if json_obj[i] is not None else {}: 61 | if json_obj_temp is not None and (k not in json_obj_temp or json_obj_temp[k] is None): 62 | json_obj_temp[k] = json_obj[i][k] 63 | json_obj_temp = None if json_obj_temp is {} else json_obj_temp 64 | analyse_json_obj(json_obj_temp, level + 1, res) 65 | 66 | else: 67 | # 针对基本数据类型,在插入的键值对数据后再加入类型序号标志位 68 | obj_type = type(json_obj) 69 | if obj_type is int: 70 | obj_type = 1 71 | elif obj_type is float: 72 | obj_type = 2 73 | elif obj_type is bool: 74 | obj_type = 3 75 | elif obj_type is str: 76 | obj_type = 4 77 | else: 78 | obj_type = 0 79 | res.append('%s<%s> : <%s>%d' % (indent * level, json_key, json_obj, obj_type)) 80 | 81 | return res 82 | 83 | 84 | def change_text(com_box, current_text): 85 | com_box.setCurrentText('List<%s>' % current_text) 86 | 87 | 88 | def change_background_color(com_box, current_text): 89 | if current_text == '': 90 | com_box.setStyleSheet("background-color:rgb(220,20,60)") 91 | elif current_text == 'Object': 92 | com_box.setStyleSheet("background-color:rgb(255,246,143)") 93 | else: 94 | com_box.setStyleSheet("") 95 | 96 | 97 | # 生成字段类型下拉框 98 | def get_type_combobox(need_connect, line): 99 | com_box = QtWidgets.QComboBox() 100 | com_box.setEditable(True) 101 | obj_type = int(line[-1]) if line[-1].isdigit() else 0 102 | global last_list_com_box 103 | 104 | com_box.currentTextChanged.connect(partial(change_background_color, com_box)) 105 | 106 | if need_connect and last_list_com_box is not None: 107 | com_box.currentTextChanged.connect(partial(change_text, last_list_com_box)) 108 | last_list_com_box = None 109 | 110 | # 根据字段数据的最后一位数字标记指定下拉框默认类型 111 | if obj_type < 8: 112 | com_box.addItem('Object') 113 | com_box.addItem('int') 114 | com_box.addItem('double') 115 | com_box.addItem('bool') 116 | com_box.addItem('String') 117 | 118 | com_box.setCurrentIndex(obj_type) 119 | elif obj_type == 8: 120 | com_box.addItem('Map') 121 | com_box.setCurrentText('') 122 | elif obj_type == 9: 123 | com_box.setCurrentText('List<>') 124 | # 将该list字段的编辑框临时保存,用于与下一个字段的类型绑定 125 | last_list_com_box = com_box 126 | 127 | # 为text至不合法的输入框设置颜色 128 | change_background_color(com_box, com_box.currentText()) 129 | return com_box 130 | 131 | 132 | def get_name_text_edit(line): 133 | te = QtWidgets.QTextEdit(line[line.find('<') + 1:line.find('>')]) 134 | 135 | return te 136 | 137 | 138 | def update_list(json_str): 139 | json_obj = json.loads(json_str) 140 | # 传入json对象,返回所需要的格式化协议数据数组 141 | res = analyse_json_obj(json_obj) 142 | global last_list_com_box 143 | last_list_com_box = None 144 | ui.tv_fields.setRowCount(len(res)) 145 | 146 | pre_type_combobox = None 147 | pre_index = -1 148 | ii = 0 149 | for i in range(len(res)): 150 | line = res[i] 151 | assert isinstance(line, str) 152 | index = line.find('<') 153 | temp_type_combobox = get_type_combobox(index != pre_index, line) 154 | pre_index = index 155 | ui.tv_fields.setCellWidget(ii, 1, temp_type_combobox) 156 | if temp_type_combobox.count() > 1 and pre_type_combobox is not None and pre_type_combobox.currentText().startswith('List'): 157 | ui.tv_fields.setRowCount(ui.tv_fields.rowCount() - 1) 158 | pre_type_combobox = temp_type_combobox 159 | continue 160 | pre_type_combobox = temp_type_combobox 161 | if line.strip() == '<[dict]>8': 162 | label = QtWidgets.QLabel("") 163 | label.setStyleSheet("background-color: rgb(200,200,200);") 164 | ui.tv_fields.setCellWidget(ii, 2, label) 165 | else: 166 | ui.tv_fields.setCellWidget(ii, 2, get_name_text_edit(line)) 167 | 168 | if index == 0: 169 | field = line.replace('<', '').replace('>', '') 170 | else: 171 | field = ("%s※==》%s" % (' ' * (index - 4), line[index:])).replace('<', '').replace('>', '') 172 | 173 | label = QtWidgets.QLabel() 174 | label.setText(field[0:60] + '...' if len(field) > 60 else field[0:-1]) 175 | label.setToolTip(field[0:-1]) 176 | 177 | ui.tv_fields.setCellWidget(ii, 0, label) 178 | ii += 1 179 | 180 | ui.tv_fields.resizeColumnToContents(0) 181 | 182 | 183 | def json_format(): 184 | # 从文本编辑框获取json字符串 185 | json_str = ui.te_json.toPlainText() 186 | json_str = rm_invisible(json_str.strip()) 187 | if is_json(json_str): 188 | # 将格式化后的json字符串覆盖到文本编辑框中 189 | ui.te_json.setText(jformat(json_str.replace('\n', ''))) 190 | 191 | # 为了修复json中含有内容为空的对象,也就是有'{}'这种玩意时解析失败的问题,先预处理把'{}'全部替换成null 192 | # 根据json更新表格条目 193 | update_list(json_str.replace('{}', 'null')) 194 | 195 | else: 196 | msg = QtWidgets.QMessageBox() 197 | msg.information(ui.te_json, "警告", "JSON不合法", QtWidgets.QMessageBox.Ok) 198 | 199 | 200 | def generate_bean(): 201 | bean = [] 202 | for i in range(ui.tv_fields.rowCount()): 203 | var_field = ui.tv_fields.cellWidget(i, 0) 204 | var_type = ui.tv_fields.cellWidget(i, 1) 205 | var_name = ui.tv_fields.cellWidget(i, 2) 206 | 207 | bean.append([var_field, var_type, var_name]) 208 | 209 | try: 210 | res = check_and_generate_code(bean) 211 | except IndexError as e: 212 | print(e) 213 | QMessageBox().information(msg_box_ui, "警告", "发生错误", QMessageBox.Ok) 214 | return 215 | if res != '': 216 | ui.te_json.setText(rm_invisible(res.strip())) 217 | 218 | 219 | def str_to_camel_case(text): 220 | try: 221 | arr = filter(None, text.split('_')) 222 | res = '' 223 | for i in arr: 224 | res = res + i[0].upper() + i[1:] 225 | return res[0].lower() + res[1:] 226 | except IndexError: 227 | return text 228 | 229 | 230 | def convert_names_to_camel_case(index): 231 | if ui.tv_fields.horizontalHeaderItem(index).text() == 'Name(click to camelCase)': 232 | for i in range(ui.tv_fields.rowCount()): 233 | name_cell = ui.tv_fields.cellWidget(i, 2) 234 | if type(name_cell) is QtWidgets.QTextEdit: 235 | assert isinstance(name_cell, QtWidgets.QTextEdit) 236 | name_cell.setText(str_to_camel_case(name_cell.toPlainText())) 237 | 238 | 239 | def init_event(): 240 | # 绑定json解析按钮事件 241 | ui.btn_format.clicked.connect(json_format) 242 | ui.btn_generate.clicked.connect(generate_bean) 243 | ui.btn_copy.clicked.connect(copy_left_text) 244 | ui.tv_fields.horizontalHeader().sectionClicked.connect(convert_names_to_camel_case) 245 | 246 | 247 | def copy_left_text(): 248 | text = ui.te_json.toPlainText() 249 | pyperclip.copy(text) 250 | pass 251 | 252 | 253 | # 设置表格基础样式 254 | def init_table(): 255 | # 设置表头,表头文字居中 256 | ui.tv_fields.setHorizontalHeaderLabels(['Fields', 'Types', 'Name(click to camelCase)']) 257 | ui.tv_fields.horizontalHeader().setDefaultAlignment(QtCore.Qt.AlignHCenter) 258 | # 表头自动平分宽度 259 | ui.tv_fields.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Stretch) 260 | # 设置第一列为固定宽度,不参与平分 261 | ui.tv_fields.horizontalHeader().setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed) 262 | ui.tv_fields.resizeColumnToContents(0) 263 | # 隐藏左侧垂直表头 264 | ui.tv_fields.verticalHeader().setVisible(False) 265 | 266 | 267 | def init_view(): 268 | init_table() 269 | 270 | 271 | def custom_ui(): 272 | init_view() 273 | init_event() 274 | 275 | 276 | if __name__ == "__main__": 277 | import sys 278 | 279 | app = QtWidgets.QApplication(sys.argv) 280 | widget = QtWidgets.QMainWindow() 281 | ui = Ui_MainWindow() 282 | ui.setupUi(widget) 283 | # 在生成代码的基础上再修改UI以及添加逻辑 284 | custom_ui() 285 | widget.show() 286 | code = check_version() 287 | widget.setWindowTitle(widget.windowTitle().replace('code', str(code) + '-nullsafety')) 288 | sys.exit(app.exec_()) 289 | -------------------------------------------------------------------------------- /logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debuggerx01/JSONFormat4Flutter/5b24ca47bce33524e0c1919c87f7c76a1457f8fb/logo.ico -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debuggerx01/JSONFormat4Flutter/5b24ca47bce33524e0c1919c87f7c76a1457f8fb/logo.png -------------------------------------------------------------------------------- /logo.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | logo.png 4 | 5 | 6 | -------------------------------------------------------------------------------- /mainwindow.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'mainwindow.ui' 4 | # 5 | # Created by: PyQt5 UI code generator 5.6 6 | # 7 | # WARNING! All changes made in this file will be lost! 8 | 9 | from PyQt5 import QtCore, QtGui, QtWidgets 10 | 11 | class Ui_MainWindow(object): 12 | def setupUi(self, MainWindow): 13 | MainWindow.setObjectName("MainWindow") 14 | MainWindow.resize(927, 716) 15 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) 16 | sizePolicy.setHorizontalStretch(0) 17 | sizePolicy.setVerticalStretch(0) 18 | sizePolicy.setHeightForWidth(MainWindow.sizePolicy().hasHeightForWidth()) 19 | MainWindow.setSizePolicy(sizePolicy) 20 | MainWindow.setMinimumSize(QtCore.QSize(800, 600)) 21 | MainWindow.setMaximumSize(QtCore.QSize(16777215, 16777215)) 22 | icon = QtGui.QIcon() 23 | icon.addPixmap(QtGui.QPixmap(":/img/logo.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) 24 | MainWindow.setWindowIcon(icon) 25 | self.centralWidget = QtWidgets.QWidget(MainWindow) 26 | self.centralWidget.setObjectName("centralWidget") 27 | self.verticalLayout = QtWidgets.QVBoxLayout(self.centralWidget) 28 | self.verticalLayout.setContentsMargins(11, 11, 11, 11) 29 | self.verticalLayout.setSpacing(6) 30 | self.verticalLayout.setObjectName("verticalLayout") 31 | self.frame = QtWidgets.QFrame(self.centralWidget) 32 | self.frame.setFrameShape(QtWidgets.QFrame.StyledPanel) 33 | self.frame.setFrameShadow(QtWidgets.QFrame.Raised) 34 | self.frame.setObjectName("frame") 35 | self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.frame) 36 | self.verticalLayout_4.setContentsMargins(11, 11, 11, 11) 37 | self.verticalLayout_4.setSpacing(6) 38 | self.verticalLayout_4.setObjectName("verticalLayout_4") 39 | self.splitter = QtWidgets.QSplitter(self.frame) 40 | self.splitter.setOrientation(QtCore.Qt.Horizontal) 41 | self.splitter.setObjectName("splitter") 42 | self.layoutWidget = QtWidgets.QWidget(self.splitter) 43 | self.layoutWidget.setObjectName("layoutWidget") 44 | self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.layoutWidget) 45 | self.verticalLayout_3.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint) 46 | self.verticalLayout_3.setContentsMargins(11, 11, 11, 11) 47 | self.verticalLayout_3.setSpacing(6) 48 | self.verticalLayout_3.setObjectName("verticalLayout_3") 49 | self.btn_format = QtWidgets.QPushButton(self.layoutWidget) 50 | self.btn_format.setObjectName("btn_format") 51 | self.verticalLayout_3.addWidget(self.btn_format) 52 | self.te_json = QtWidgets.QTextEdit(self.layoutWidget) 53 | self.te_json.setObjectName("te_json") 54 | self.verticalLayout_3.addWidget(self.te_json) 55 | self.btn_copy = QtWidgets.QPushButton(self.layoutWidget) 56 | self.btn_copy.setObjectName("btn_copy") 57 | self.verticalLayout_3.addWidget(self.btn_copy) 58 | self.layoutWidget1 = QtWidgets.QWidget(self.splitter) 59 | self.layoutWidget1.setObjectName("layoutWidget1") 60 | self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.layoutWidget1) 61 | self.verticalLayout_2.setContentsMargins(11, 11, 11, 11) 62 | self.verticalLayout_2.setSpacing(6) 63 | self.verticalLayout_2.setObjectName("verticalLayout_2") 64 | self.btn_generate = QtWidgets.QPushButton(self.layoutWidget1) 65 | self.btn_generate.setObjectName("btn_generate") 66 | self.verticalLayout_2.addWidget(self.btn_generate) 67 | self.tv_fields = QtWidgets.QTableWidget(self.layoutWidget1) 68 | self.tv_fields.setColumnCount(3) 69 | self.tv_fields.setObjectName("tv_fields") 70 | self.tv_fields.setRowCount(0) 71 | self.verticalLayout_2.addWidget(self.tv_fields) 72 | self.verticalLayout_4.addWidget(self.splitter) 73 | self.verticalLayout.addWidget(self.frame) 74 | MainWindow.setCentralWidget(self.centralWidget) 75 | 76 | self.retranslateUi(MainWindow) 77 | QtCore.QMetaObject.connectSlotsByName(MainWindow) 78 | 79 | def retranslateUi(self, MainWindow): 80 | _translate = QtCore.QCoreApplication.translate 81 | MainWindow.setWindowTitle(_translate("MainWindow", "JSONFormat4Flutter(vcode)")) 82 | self.btn_format.setText(_translate("MainWindow", "格式化")) 83 | self.btn_copy.setText(_translate("MainWindow", "复制")) 84 | self.btn_generate.setText(_translate("MainWindow", "生成Bean")) 85 | 86 | import logo_rc 87 | 88 | if __name__ == "__main__": 89 | import sys 90 | 91 | app = QtWidgets.QApplication(sys.argv) 92 | widget = QtWidgets.QMainWindow() 93 | ui = Ui_MainWindow() 94 | ui.setupUi(widget) 95 | widget.show() 96 | sys.exit(app.exec_()) 97 | 98 | -------------------------------------------------------------------------------- /mainwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 927 10 | 716 11 | 12 | 13 | 14 | 15 | 0 16 | 0 17 | 18 | 19 | 20 | 21 | 800 22 | 600 23 | 24 | 25 | 26 | 27 | 16777215 28 | 16777215 29 | 30 | 31 | 32 | JSONFormat4Flutter(vcode) 33 | 34 | 35 | 36 | :/img/logo.png:/img/logo.png 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | QFrame::StyledPanel 45 | 46 | 47 | QFrame::Raised 48 | 49 | 50 | 51 | 52 | 53 | Qt::Horizontal 54 | 55 | 56 | 57 | 58 | QLayout::SetDefaultConstraint 59 | 60 | 61 | 62 | 63 | 格式化 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 复制 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 生成Bean 85 | 86 | 87 | 88 | 89 | 90 | 91 | 3 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /template_code.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding:utf-8 -*- 3 | # @Filename : code_top.py 4 | # @Date : 18-1-30 下午2:37 5 | # @Author : DebuggerX 6 | 7 | 8 | template_dict = r""" 9 | 10 | class ${type} { 11 | 12 | ${properties} 13 | 14 | ${type}.fromParams({${this.properties}}); 15 | 16 | ${type}.fromJson(jsonRes) { 17 | ${construction} 18 | } 19 | 20 | @override 21 | String toString() { 22 | return '{${toString}}'; 23 | } 24 | 25 | String toJson() => this.toString(); 26 | } 27 | 28 | """ 29 | 30 | template_list = r""" 31 | ${list_count}${class_type}${>count} ${name}${current_child} = ${name}${parent_items} == null ? null : []; 32 | for (var ${name}${current_items} in ${name}${current_child} == null ? [] : ${name}${parent_items}){ 33 | ${loop} 34 | ${name}${current_child}!.add(${name}${child_child}); 35 | } 36 | """ 37 | 38 | 39 | def get_top_code_dict(t): 40 | return template_dict.replace('${type}', t) 41 | 42 | 43 | def get_list_code_loop(list_count, ct, ab_count, n, current_child, child_child, current_items, parent_items): 44 | return template_list \ 45 | .replace('${list_count}', list_count) \ 46 | .replace('${class_type}', ct) \ 47 | .replace('${>count}', ab_count) \ 48 | .replace('${name}', n) \ 49 | .replace('${current_child}', current_child) \ 50 | .replace('${child_child}', child_child) \ 51 | .replace('${current_items}', current_items) \ 52 | .replace('${parent_items}', parent_items) 53 | -------------------------------------------------------------------------------- /tools.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding:utf-8 -*- 3 | # @Filename : tools.py 4 | # @Date : 18-1-30 上午11:48 5 | # @Author : DebuggerX 6 | 7 | import json 8 | 9 | from string import printable 10 | 11 | from PyQt5.QtWidgets import QLabel, QComboBox, QMessageBox 12 | 13 | from template_code import get_top_code_dict, get_list_code_loop 14 | 15 | msg_box_ui = None 16 | 17 | 18 | def rm_invisible(string): 19 | return ''.join(char for char in string if char in printable) 20 | 21 | 22 | # 验证json字符串是否合法 23 | def is_json(my_json): 24 | try: 25 | j = json.loads(my_json) 26 | except ValueError: 27 | return False 28 | if type(j) in (list, dict): 29 | return True 30 | return False 31 | 32 | 33 | # 传入未格式化的单行json字符串,返回指定缩进的多行json字符串 34 | def jformat(inp): 35 | obj = json.loads(inp) 36 | outp = json.dumps(obj, skipkeys=False, ensure_ascii=False, check_circular=True, allow_nan=True, cls=None, indent=' ', separators=None, 37 | default=None, sort_keys=True) 38 | return outp 39 | 40 | 41 | def check_level_type(t): 42 | if t == 'Map': 43 | # 不用处理的Map类型 44 | return 0 45 | if t in ('int', 'double', 'bool', 'Object'): 46 | # 普通数据类型 47 | return 1 48 | if t == 'String': 49 | # 字符串类型 50 | return 2 51 | if t.startswith('List<'): 52 | # 数组类型 53 | return 3 54 | # 字典类型 55 | return 4 56 | 57 | 58 | def list_code_loop(code, count, total, n, ct): 59 | list_count = 'List<' * (total - count) 60 | ab_count = '?>' * (total - count) + '?' 61 | 62 | current_child = 'Child' * count 63 | child_child = 'Child' * (count + 1) 64 | 65 | current_items = 'Item' * (count + 1) 66 | parent_items = 'Item' * count 67 | 68 | fragment = get_list_code_loop(list_count, ct, ab_count, n, current_child, child_child, current_items, parent_items) 69 | 70 | if code == '': 71 | return fragment 72 | else: 73 | return code.replace('){\n', '){').replace('${loop}\n', fragment) 74 | 75 | 76 | # 向代码模板中插入变量 77 | def build_list_construction(t, f, n): 78 | class_type = t.replace('List<', '').replace('>', '').replace('?', '') 79 | 80 | list_loop = 'jsonRes[\'%s\'] == null ? null : [];\n' % f 81 | assert isinstance(t, str) 82 | 83 | code = '' 84 | 85 | total = t.count('>') 86 | for i in range(total): 87 | code = list_code_loop(code, i, total, n, class_type) 88 | 89 | # 嵌套模板的后续处理 90 | if check_level_type(class_type) not in (1, 2) and class_type != '': 91 | code = code.replace('%s%s' % (n, 'Child' * total), '%s%s == null ? null : %s.fromJson(%s%s)' 92 | % (n, ('Item' * total), class_type, n, ('Item' * total))) 93 | else: 94 | code = code.replace('%s' % ('Child' * total), '%s' % ('Item' * total)) 95 | code = code[code.find(';') + 1:] 96 | code = code.replace('%s){' % n, 'jsonRes[\'%s\']){' % n).replace('${loop}\n', '').replace('jsonRes[\'%s\']){' % n, 'jsonRes[\'%s\']){' % f) 97 | 98 | return list_loop + code 99 | 100 | 101 | def add_param_to_code(code, param): 102 | (f, t, n) = param 103 | 104 | # 先按照基本数据类型方式处理 105 | properties = ' %s? %s;\n' % (t, n) 106 | this_properties = 'this.%s, ' % n 107 | construction = ' %s = jsonRes[\'%s\'];\n' % (n, f) 108 | to_string = '"%s": $%s, ' % (f, n) 109 | 110 | pp = code.find('${properties}') 111 | code = code[:pp] + properties + code[pp:] 112 | 113 | ptp = code.find('${this.properties}') 114 | code = code[:ptp] + this_properties + code[ptp:] 115 | 116 | pc = code.find('${construction}') 117 | code = code[:pc] + construction + code[pc:] 118 | 119 | ps = code.find('${toString}') 120 | code = code[:ps] + to_string + code[ps:] 121 | 122 | t_code = check_level_type(t) 123 | 124 | # 字符串类型、List类型和Map类型处理,需要修改toString中的输出方式 125 | if t_code in [0, 2] or 'List' in t: 126 | code = code.replace(': $%s' % n, ': ${%s != null?\'${json.encode(%s)}\':\'null\'}' % (n, n)) 127 | 128 | # dict类型处理,只需要修改construction中的输出方式 129 | elif t_code == 4: 130 | code = code.replace('jsonRes[\'%s\']' % f, 'jsonRes[\'%s\'] == null ? null : %s.fromJson(jsonRes[\'%s\'])' % (f, t, f)) 131 | 132 | # list类型处理,只需要修改construction中的输出方式 133 | if t_code == 3: 134 | list_loop = build_list_construction(t, f, n) 135 | 136 | code = code.replace('jsonRes[\'%s\'];' % f, list_loop) 137 | 138 | return code 139 | 140 | 141 | # 用来存储各个code片段,最后合并就是完整的代码 142 | codes = [] 143 | 144 | 145 | def build_level_code(level_bean): 146 | (l, f, t, n) = level_bean.pop(0) 147 | type_code = check_level_type(t) 148 | # 处理字典的子数据 149 | if type_code == 4: 150 | code = get_top_code_dict(t) 151 | work_level = level_bean[0][0] 152 | while len(level_bean) > 0: 153 | (l, f, t, n) = level_bean.pop(0) 154 | if n == '[list]': 155 | continue 156 | # 数据类型为字典时 157 | if check_level_type(t) == 4: 158 | # 先把该字典的定义作为顶层存到递归调用的bean顶部 159 | child_bean = [(l, f, t, n)] 160 | while len(level_bean) > 0 and level_bean[0][0] > work_level: 161 | child_bean.append(level_bean.pop(0)) 162 | build_level_code(child_bean) 163 | # 数据类型为数组时 164 | if check_level_type(t) == 3 and len(level_bean) > 0: 165 | generic_type = t.replace('List<', '').replace('>', '') 166 | # 如果List的里层数据为dict则对其去壳后处理 167 | if check_level_type(generic_type) == 4 and generic_type != '': 168 | while check_level_type(level_bean[0][2]) == 3: 169 | work_level = level_bean[0][0] 170 | level_bean.pop(0) 171 | 172 | child_bean = [] 173 | while len(level_bean) > 0 and level_bean[0][0] > work_level: 174 | child_bean.append(level_bean.pop(0)) 175 | build_level_code(child_bean) 176 | 177 | if t != 'List<>': 178 | t = t.replace('>', '?>') 179 | 180 | # 不管如何,到这里的数据都是目前dict的一级子数据,作为参数传入模板中 181 | code = add_param_to_code(code, (f, t, n)) 182 | codes.append(code.replace(', ${toString}', '').replace('${construction}', '').replace('${properties}', '').replace('${this.properties}', '')) 183 | 184 | 185 | def generate_code(work_bean): 186 | is_list_top = False 187 | 188 | # 如果顶级容器为list而不是dict,则先在外面包一层dict,并将list的别名传递为dict的类型,list则重命名为'list',并修改标志位供后面修改使用 189 | (l, f, t, n) = work_bean[0] 190 | if t.startswith('List<'): 191 | work_bean[0][3] = 'list' 192 | work_bean[0][1] = 'json_list' 193 | work_bean.insert(0, (-2, "", n, "")) 194 | is_list_top = True 195 | 196 | build_level_code(work_bean) 197 | 198 | res = '' 199 | codes.reverse() 200 | 201 | for c in codes: 202 | res += c 203 | 204 | codes.clear() 205 | 206 | # 如果顶级容器为list,需要修改第一次获取JSON对象的方式为直接获取 207 | if is_list_top: 208 | res = res.replace('jsonRes[\'json_list\']', 'jsonRes', 2) 209 | 210 | # 如果json中存在空list这种操蛋情况,将list类型从list<>修改成list 211 | res = res.replace('List<>', 'List') 212 | 213 | # 移除参数构造函数用模板生成后多余的逗号和空格 214 | res = res.replace(', });', '});') 215 | 216 | # 移除没有必要的list取值循环 217 | lines = res.splitlines() 218 | for index in range(len(lines)): 219 | if lines[index].strip() == '' and index < len(lines) - 1 and lines[index + 1].strip() in ('', '}'): 220 | lines[index] = '//%s' % lines[index] 221 | 222 | # 最终修改,添加json库导包代码,并为顶层对象增加默认构造 223 | out_res = 'import \'dart:convert\' show json;\n' 224 | first = True 225 | for line in lines: 226 | if line.startswith('//'): 227 | continue 228 | out_res += (line + '\n') 229 | if first and r'.fromParams({this.' in line: 230 | class_name = line.split(r'.fromParams({this.')[0].strip() 231 | out_res += '\n factory %s(Object jsonStr) => jsonStr is String ? %s.fromJson(json.decode(jsonStr)) : %s.fromJson(jsonStr);\n' \ 232 | % (class_name, class_name, class_name) 233 | out_res += '\n static %s? parse(jsonStr) => [\'null\', \'\', null].contains(jsonStr) ? null : %s(jsonStr);\n' \ 234 | % (class_name, class_name) 235 | first = False 236 | return out_res 237 | 238 | 239 | def check_and_generate_code(bean): 240 | work_bean = [] 241 | global msg_box_ui 242 | msg_box_ui = bean[0][1] 243 | ignore_level = None 244 | for f, t, n in bean: 245 | field_text = f.text() 246 | 247 | level = field_text.find('※') // 4 248 | if ignore_level is not None and level > ignore_level: 249 | continue 250 | ignore_level = None 251 | 252 | field_text = field_text[field_text.find("》") + 1: field_text.find(":") - 1] if ":" in field_text else '' 253 | type_text = t.currentText() if type(t) is QComboBox else t.toPlainText() 254 | name_text = n.text() if type(n) is QLabel else n.toPlainText() 255 | if type_text == 'Map': 256 | ignore_level = level 257 | 258 | if type_text.strip() != '' or ignore_level is not None: 259 | work_bean.append([level, field_text, type_text, name_text]) 260 | else: 261 | QMessageBox().information(msg_box_ui, "警告", "字段类型未设置", QMessageBox.Ok) 262 | return '' 263 | 264 | (l, f, t, n) = work_bean[0] 265 | if t.startswith('List<') and n == '[list]': 266 | QMessageBox().information(msg_box_ui, "警告", "请为顶层List设置别名,该别名将成为顶级对象类名", QMessageBox.Ok) 267 | return '' 268 | 269 | return generate_code(work_bean) 270 | -------------------------------------------------------------------------------- /version: -------------------------------------------------------------------------------- 1 | { 2 | "code": 0.9, 3 | "desc": "迁移至空安全语法。" 4 | } 5 | --------------------------------------------------------------------------------