├── .gitignore ├── .metadata ├── .pubignore ├── CHANGELOG.md ├── LICENSE ├── README-EN.md ├── README.md ├── analysis_options.yaml ├── bin ├── build_runner.dart ├── graph_inspector.dart ├── json_model.dart └── src │ └── commands │ ├── clean.dart │ └── generate_build_script.dart ├── example ├── README.md ├── jsons │ ├── card.json │ ├── test_dir │ │ └── profile.json │ └── user.json ├── lib │ └── demo.dart ├── pubspec.lock └── pubspec.yaml ├── lib ├── build_runner.dart ├── graph_inspector.dart ├── index.dart ├── json_model.dart └── src │ └── commands │ ├── clean.dart │ └── generate_build_script.dart ├── model.tpl ├── pubspec.lock ├── pubspec.yaml └── test └── json_model_test.dart /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # Visual Studio Code related 19 | .vscode/ 20 | 21 | # Flutter/Dart/Pub related 22 | **/doc/api/ 23 | .dart_tool/ 24 | .flutter-plugins 25 | .packages 26 | .pub-cache/ 27 | .pub/ 28 | build/ 29 | 30 | # Android related 31 | **/android/**/gradle-wrapper.jar 32 | **/android/.gradle 33 | **/android/captures/ 34 | **/android/gradlew 35 | **/android/gradlew.bat 36 | **/android/local.properties 37 | **/android/**/GeneratedPluginRegistrant.java 38 | 39 | # iOS/XCode related 40 | **/ios/**/*.mode1v3 41 | **/ios/**/*.mode2v3 42 | **/ios/**/*.moved-aside 43 | **/ios/**/*.pbxuser 44 | **/ios/**/*.perspectivev3 45 | **/ios/**/*sync/ 46 | **/ios/**/.sconsign.dblite 47 | **/ios/**/.tags* 48 | **/ios/**/.vagrant/ 49 | **/ios/**/DerivedData/ 50 | **/ios/**/Icon? 51 | **/ios/**/Pods/ 52 | **/ios/**/.symlinks/ 53 | **/ios/**/profile 54 | **/ios/**/xcuserdata 55 | **/ios/.generated/ 56 | **/ios/Flutter/App.framework 57 | **/ios/Flutter/Flutter.framework 58 | **/ios/Flutter/Generated.xcconfig 59 | **/ios/Flutter/app.flx 60 | **/ios/Flutter/app.zip 61 | **/ios/Flutter/flutter_assets/ 62 | **/ios/ServiceDefinitions.json 63 | **/ios/Runner/GeneratedPluginRegistrant.* 64 | 65 | # Exceptions to above rules. 66 | !**/ios/**/default.mode1v3 67 | !**/ios/**/default.mode2v3 68 | !**/ios/**/default.pbxuser 69 | !**/ios/**/default.perspectivev3 70 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 71 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 482078387ed9610cf52680910816db2f73318922 8 | channel: master 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /.pubignore: -------------------------------------------------------------------------------- 1 | json_model.iml 2 | analysis_options.yaml 3 | model.tpl -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | ## [1.0.0] 3 | * release version 4 | ## [1.0.0-beta1] 5 | 6 | * first release version 7 | 8 | ## [0.0.2] - 2019.5.10 9 | 10 | * update doc and demo 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT 2 | -------------------------------------------------------------------------------- /README-EN.md: -------------------------------------------------------------------------------- 1 | 2 | Language: [English](README.md) | [中文简体](README-ZH.md) 3 | 4 | 5 | # json_model [![Pub](https://img.shields.io/pub/v/json_model.svg?style=flat-square)](https://pub.dartlang.org/packages/json_model) 6 | 7 | Generating Dart model class from Json files with one command. 8 | 9 | ## Installing 10 | 11 | ```yaml 12 | dev_dependencies: 13 | json_model: ^1.0.0 14 | json_serializable: ^5.0.0 15 | ``` 16 | 17 | ## Getting Started 18 | 19 | 1. Create a "jsons" directory in the root of your project; 20 | 2. Create a Json file under "jsons" dir ; 21 | 3. Run `flutter packages pub run json_model` (in Flutter) or `pub run json_model` (in Dart VM) 22 | 23 | ## Examples 24 | 25 | File: `jsons/user.json` 26 | 27 | ```javascript 28 | { 29 | "name":"wendux", 30 | "father":"$user", //Other class model , use '$' 31 | "friends":"$[]user", // Array 32 | "keywords":"$[]String", // Array 33 | "age?":20 34 | } 35 | ``` 36 | 37 | Run `pub run json_model`, then you'll see the generated json file under `lib/models/` dir: 38 | 39 | ```dart 40 | import 'package:json_annotation/json_annotation.dart'; 41 | 42 | part 'user.g.dart'; 43 | 44 | @JsonSerializable() 45 | class User { 46 | User(); 47 | 48 | late String name; 49 | late User father; 50 | late List friends; 51 | late List keywords; 52 | num? age; 53 | 54 | factory User.fromJson(Map json) => _$UserFromJson(json); 55 | Map toJson() => _$UserToJson(this); 56 | } 57 | ``` 58 | 59 | ### @JsonKey 60 | 61 | You can also use “@JsonKey” annotation from [json_annotation](https://pub.dev/packages/json_annotation) package. 62 | 63 | ```javascript 64 | { 65 | "@JsonKey(name: '+1') int?": "loved", //将“+1”映射为“loved” 66 | "name":"wendux", 67 | "age?":20 68 | } 69 | ``` 70 | 71 | The generated class is: 72 | 73 | ```dart 74 | import 'package:json_annotation/json_annotation.dart'; 75 | part 'user.g.dart'; 76 | 77 | @JsonSerializable() 78 | class User { 79 | User(); 80 | @JsonKey(name: '+1') int? loved; 81 | late String name; 82 | num? age; 83 | 84 | factory User.fromJson(Map json) => _$UserFromJson(json); 85 | Map toJson() => _$UserToJson(this); 86 | } 87 | ``` 88 | 89 | Test: 90 | 91 | ```dart 92 | import 'models/index.dart'; 93 | 94 | void main() { 95 | var u = User.fromJson({"name": "Jack", "age": 16, "+1": 20}); 96 | print(u.loved); // 20 97 | } 98 | ``` 99 | 100 | ### $meta 101 | 102 | ```javascript 103 | "@meta": { 104 | "import": [ //import file for model class 105 | "test_dir/profile.dart" 106 | ], 107 | "comments": { // comments for fields 108 | "name": "nick name" 109 | }, 110 | "nullable": true, // fields are nullable? 111 | "ignore": false // Wether skip this json file when generating dart model class. 112 | } 113 | ``` 114 | 115 | user.json 116 | 117 | ```dart 118 | { 119 | "@meta": { 120 | "import": [ 121 | "test_dir/profile.dart" 122 | ], 123 | "comments": { 124 | "name": "名字" 125 | }, 126 | "nullable": true 127 | }, 128 | "@JsonKey(ignore: true) Profile?": "profile", 129 | "@JsonKey(name: '+1') int?": "loved", 130 | "name": "wendux", 131 | "father": "$user", 132 | "friends": "$[]user", 133 | "keywords": "$[]String", 134 | "age?": 20 135 | } 136 | ``` 137 | 138 | The generated class: 139 | 140 | ```dart 141 | import 'package:json_annotation/json_annotation.dart'; 142 | import 'test_dir/profile.dart'; 143 | part 'user.g.dart'; 144 | 145 | @JsonSerializable() 146 | class User { 147 | User(); 148 | 149 | @JsonKey(ignore: true) Profile? profile; 150 | @JsonKey(name: '+1') int? loved; 151 | //nick name 152 | String? name; 153 | User? father; 154 | List? friends; 155 | List? keywords; 156 | num? age; 157 | 158 | factory User.fromJson(Map json) => _$UserFromJson(json); 159 | Map toJson() => _$UserToJson(this); 160 | } 161 | ``` 162 | 163 | For completed examples see [here](https://github.com/flutterchina/json_model/tree/master/example) . 164 | 165 | ## Command arguments 166 | 167 | The default json source file directory is ` project_root/jsons`; you can custom the src file directory by `src` argument, for example: 168 | 169 | ```shell 170 | pub run json_model src=json_files 171 | ``` 172 | 173 | You can also custom the dist directory by `dist` argument: 174 | 175 | ```shell 176 | pub run json_model src=json_files dist=data # will save in lib/data dir 177 | ``` 178 | 179 | > The `dist` root is `lib` 180 | 181 | You can spcify the --nullable flag to make fields of dart model class defauts to nullable. 182 | 183 | ```shell 184 | pub run json_model --nullable 185 | ``` 186 | 187 | ## Run by code 188 | 189 | If you want to run json_model by code instead command line, you can: 190 | 191 | ```dart 192 | import 'package:json_model/json_model.dart'; 193 | void main() { 194 | run(['src=jsons']); //run 195 | } 196 | ``` 197 | 198 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 语言: [English](README.md) | [中文简体](README-ZH.md) 3 | 4 | 5 | # json_model [![Pub](https://img.shields.io/pub/v/json_model.svg?style=flat-square)](https://pub.dartlang.org/packages/json_model) 6 | 7 | 一行命令,将Json文件转为Dart model类。 8 | 9 | ## 安装 10 | 11 | ```yaml 12 | dev_dependencies: 13 | json_model: ^1.0.0 14 | json_serializable: ^5.0.0 15 | ``` 16 | 17 | ## 使用 18 | 19 | 1. 在工程根目录下创建一个名为 "jsons" 的目录; 20 | 2. 创建或拷贝Json文件到"jsons" 目录中 ; 21 | 3. 运行 `pub run json_model` (Dart VM工程)or `flutter packages pub run json_model`(Flutter中) 命令生成Dart model类,生成的文件默认在"lib/models"目录下 22 | 23 | ## 思想 24 | 25 | 大多数开发者可能都是通过UI工具来将Json文件来生成Dart model类。这会有一个小问题,一旦生成Dart model类后,原始的json数据是不会维护的,但现实开发中偶尔会有查看原始Json数据的需求。json_model的主要思路就是项目中**只维护json文件,而不用去关注生成的dart文件,只要json文件在,随时都可以通过一句命令生成dart类**。 26 | 27 | json_model还有一个优势是在多人协作的项目中,可以集成到构建流程中,无需每个人都去安装一个转换工具。 28 | 29 | 当然,这只是一个小差异,如果你更喜欢UI工具的方式,按照自己喜欢的方式来就行。 30 | 31 | ## 例子 32 | 33 | Json文件: `jsons/user.json` 34 | 35 | ```javascript 36 | { 37 | "name":"wendux", 38 | "father":"$user", //可以通过"$"符号引用其它model类, 这个是引用User类 39 | "friends":"$[]user", // 可以通过"$[]"来引用数组 40 | "keywords":"$[]String", // 同上 41 | "age?":20 // 年龄,可能为null 42 | } 43 | ``` 44 | 45 | 生成的Dart model类: 46 | 47 | ```dart 48 | import 'package:json_annotation/json_annotation.dart'; 49 | 50 | part 'user.g.dart'; 51 | 52 | @JsonSerializable() 53 | class User { 54 | User(); 55 | 56 | late String name; 57 | late User father; 58 | late List friends; 59 | late List keywords; 60 | num? age; 61 | 62 | factory User.fromJson(Map json) => _$UserFromJson(json); 63 | Map toJson() => _$UserToJson(this); 64 | } 65 | ``` 66 | 67 | ### Json标注 68 | 69 | 您也可以使用[json_annotation](https://pub.dev/packages/json_annotation)包中定义的所有标注。 70 | 71 | 比如Json文件中有一个字段名为"+1",由于在转成Dart类后,字段名会被当做变量名,但是在Dart中变量名不能包含“+”,我们可以通过“@JsonKey”来映射变量名; 72 | 73 | ```javascript 74 | { 75 | "@JsonKey(name: '+1') int?": "loved", //将“+1”映射为“loved” 76 | "name":"wendux", 77 | "age?":20 78 | } 79 | ``` 80 | 81 | 生成文件如下: 82 | 83 | ```dart 84 | import 'package:json_annotation/json_annotation.dart'; 85 | part 'user.g.dart'; 86 | 87 | @JsonSerializable() 88 | class User { 89 | User(); 90 | @JsonKey(name: '+1') int? loved; 91 | late String name; 92 | num? age; 93 | 94 | factory User.fromJson(Map json) => _$UserFromJson(json); 95 | Map toJson() => _$UserToJson(this); 96 | } 97 | ``` 98 | 99 | 测试: 100 | 101 | ```dart 102 | import 'models/index.dart'; 103 | 104 | void main() { 105 | var u = User.fromJson({"name": "Jack", "age": 16, "+1": 20}); 106 | print(u.loved); // 20 107 | } 108 | ``` 109 | 110 | > 关于 `@JsonKey`标注的详细内容请参考[json_annotation](https://pub.dev/packages/json_annotation) 包; 111 | 112 | 113 | 114 | ### $meta 配置 115 | 116 | 另外,在json文件中我们可以通过指定`$meta` 信息来定制生成Dart文件的规则: 117 | 118 | ```javascript 119 | "@meta": { 120 | "import": [ // 生成的Dart文件中需要额外import的文件,可以有多个 121 | "test_dir/profile.dart" 122 | ], 123 | "comments": { // Json文件中相应字段的注释 124 | "name": "名字" 125 | }, 126 | "nullable": true, // 是否生成的model字段都是可空的,如果为false,则字段为late 127 | "ignore": false // json->dart时是否跳过当前文件,默认为false,设为true时,生成时则会跳过本json文件 128 | } 129 | ``` 130 | 131 | > `ignore` 配置在“需要手动修改自动生成的代码”的场景下非常实用。比如在首次生成之后dart文件后,如果我们需要添加一些逻辑,比如给model类添加了一个方法,如果后续再运行自动生成命令,则我们修改的类会被重新生成的代码覆盖掉,解决这个问题的方式就是修改后将ignore置为true,这样重新执行自动生成时会跳过该json文件。 132 | 133 | 例子: 134 | 135 | user.json 136 | 137 | ```dart 138 | { 139 | "@meta": { 140 | "import": [ 141 | "test_dir/profile.dart" 142 | ], 143 | "comments": { 144 | "name": "名字" 145 | }, 146 | "nullable": true 147 | }, 148 | "@JsonKey(ignore: true) Profile?": "profile", 149 | "@JsonKey(name: '+1') int?": "loved", 150 | "name": "wendux", 151 | "father": "$user", 152 | "friends": "$[]user", 153 | "keywords": "$[]String", 154 | "age?": 20 155 | } 156 | ``` 157 | 158 | 生成文件如下: 159 | 160 | ```dart 161 | import 'package:json_annotation/json_annotation.dart'; 162 | import 'test_dir/profile.dart'; 163 | part 'user.g.dart'; 164 | 165 | @JsonSerializable() 166 | class User { 167 | User(); 168 | 169 | @JsonKey(ignore: true) Profile? profile; 170 | @JsonKey(name: '+1') int? loved; 171 | //名字 172 | String? name; 173 | User? father; 174 | List? friends; 175 | List? keywords; 176 | num? age; 177 | 178 | factory User.fromJson(Map json) => _$UserFromJson(json); 179 | Map toJson() => _$UserToJson(this); 180 | } 181 | ``` 182 | 183 | 184 | 185 | ## 全局命令参数 186 | 187 | 默认的源json文件目录为根目录下名为 "json" 的目录;可以通过 `src` 参数自定义源json文件目录,例如: 188 | 189 | ```shell 190 | pub run json_model src=json_files 191 | ``` 192 | 193 | 默认的生成目录为"lib/models",同样也可以通过`dist` 参数来自定义输出目录: 194 | 195 | ```shell 196 | pub run json_model src=json_files dist=data # 输出目录为 lib/data 197 | ``` 198 | 199 | > 注意,dist会默认已lib为根目录。 200 | 201 | 默认的生成的字段都不是可空类型,我们可以通过设置--nullable参数来切换生成的类型为可空类型: 202 | 203 | ```shell 204 | pub run json_model --nullable 205 | ``` 206 | 207 | > 注意:当json文件中的@meta中配置了nullable后会覆盖全局命令配置,也就是说json文件中的配置优先生效。 208 | 209 | ## 代码调用 210 | 211 | 如果您正在开发一个工具,想在代码中使用json_model,此时便不能通过命令行来调用json_model,这是你可以通过代码调用: 212 | 213 | ```dart 214 | import 'package:json_model/json_model.dart'; 215 | void main() { 216 | run(['src=jsons']); //run方法为json_model暴露的方法; 217 | } 218 | ``` 219 | 220 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:lint/analysis_options.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at 17 | # https://dart-lang.github.io/linter/lints/index.html. 18 | # 19 | # Instead of disabling a lint rule for the entire project in the 20 | # section below, it can also be suppressed for a single line of code 21 | # or a specific dart file by using the `// ignore: name_of_lint` and 22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 23 | # producing the lint. 24 | rules: 25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 27 | parameter_assignments:true 28 | 29 | # Additional information about this file can be found at 30 | # https://dart.dev/guides/language/analysis-options 31 | -------------------------------------------------------------------------------- /bin/build_runner.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:io'; 7 | 8 | import 'package:args/args.dart'; 9 | import 'package:args/command_runner.dart'; 10 | import 'package:build_runner/src/build_script_generate/bootstrap.dart'; 11 | import 'package:build_runner/src/entrypoint/options.dart'; 12 | import 'package:build_runner/src/entrypoint/runner.dart'; 13 | import 'package:build_runner/src/logging/std_io_logging.dart'; 14 | import 'package:build_runner_core/build_runner_core.dart'; 15 | import 'package:io/ansi.dart'; 16 | import 'package:io/io.dart'; 17 | import 'package:logging/logging.dart'; 18 | 19 | import 'src/commands/clean.dart'; 20 | import 'src/commands/generate_build_script.dart'; 21 | 22 | Future run(List args) async { 23 | // Use the actual command runner to parse the args and immediately print the 24 | // usage information if there is no command provided or the help command was 25 | // explicitly invoked. 26 | var commandRunner = 27 | BuildCommandRunner([], await PackageGraph.forThisPackage()); 28 | var localCommands = [CleanCommand(), GenerateBuildScript()]; 29 | var localCommandNames = localCommands.map((c) => c.name).toSet(); 30 | for (var command in localCommands) { 31 | commandRunner.addCommand(command); 32 | // This flag is added to each command individually and not the top level. 33 | command.argParser.addFlag(verboseOption, 34 | abbr: 'v', 35 | defaultsTo: false, 36 | negatable: false, 37 | help: 'Enables verbose logging.'); 38 | } 39 | 40 | ArgResults parsedArgs; 41 | try { 42 | parsedArgs = commandRunner.parse(args); 43 | } on UsageException catch (e) { 44 | print(red.wrap(e.message)); 45 | print(''); 46 | print(e.usage); 47 | exitCode = ExitCode.usage.code; 48 | return; 49 | } 50 | 51 | var commandName = parsedArgs.command?.name; 52 | 53 | if (parsedArgs.rest.isNotEmpty) { 54 | print( 55 | yellow.wrap('Could not find a command named "${parsedArgs.rest[0]}".')); 56 | print(''); 57 | print(commandRunner.usageWithoutDescription); 58 | exitCode = ExitCode.usage.code; 59 | return; 60 | } 61 | 62 | if (commandName == 'help' || 63 | parsedArgs.wasParsed('help') || 64 | (parsedArgs.command?.wasParsed('help') ?? false)) { 65 | await commandRunner.runCommand(parsedArgs); 66 | return; 67 | } 68 | 69 | if (commandName == null) { 70 | commandRunner.printUsage(); 71 | exitCode = ExitCode.usage.code; 72 | return; 73 | } 74 | 75 | StreamSubscription logListener; 76 | if (commandName == 'daemon') { 77 | // Simple logs only in daemon mode. These get converted into info or 78 | // severe logs by the client. 79 | logListener = Logger.root.onRecord.listen((record) { 80 | if (record.level >= Level.SEVERE) { 81 | var buffer = StringBuffer(record.message); 82 | if (record.error != null) buffer.writeln(record.error); 83 | if (record.stackTrace != null) buffer.writeln(record.stackTrace); 84 | stderr.writeln(buffer); 85 | } else { 86 | stdout.writeln(record.message); 87 | } 88 | }); 89 | } else { 90 | var verbose = parsedArgs.command!['verbose'] as bool? ?? false; 91 | if (verbose) Logger.root.level = Level.ALL; 92 | logListener = 93 | Logger.root.onRecord.listen(stdIOLogListener(verbose: verbose)); 94 | } 95 | if (localCommandNames.contains(commandName)) { 96 | exitCode = await commandRunner.runCommand(parsedArgs) ?? 1; 97 | } else { 98 | while ((exitCode = await generateAndRun(args)) == ExitCode.tempFail.code) {} 99 | } 100 | await logListener.cancel(); 101 | } 102 | -------------------------------------------------------------------------------- /bin/graph_inspector.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:io'; 7 | 8 | import 'package:args/args.dart'; 9 | import 'package:args/command_runner.dart'; 10 | import 'package:build/build.dart'; 11 | import 'package:build_runner_core/build_runner_core.dart'; 12 | import 'package:build_runner_core/src/asset_graph/graph.dart'; 13 | import 'package:build_runner_core/src/asset_graph/node.dart'; 14 | import 'package:glob/glob.dart'; 15 | import 'package:logging/logging.dart'; 16 | import 'package:path/path.dart' as p; 17 | 18 | late final AssetGraph assetGraph; 19 | late final PackageGraph packageGraph; 20 | 21 | final logger = Logger('graph_inspector'); 22 | 23 | Future main(List args) async { 24 | final logSubscription = 25 | Logger.root.onRecord.listen((record) => print(record.message)); 26 | logger.warning( 27 | 'Warning: this tool is unsupported and usage may change at any time, ' 28 | 'use at your own risk.'); 29 | 30 | final argParser = ArgParser() 31 | ..addOption('graph-file', 32 | abbr: 'g', help: 'Specify the asset_graph.json file to inspect.') 33 | ..addOption('build-script', 34 | abbr: 'b', 35 | help: 'Specify the build script to find the asset graph for.', 36 | defaultsTo: '.dart_tool/build/entrypoint/build.dart'); 37 | 38 | final results = argParser.parse(args); 39 | 40 | if (results.wasParsed('graph-file') && results.wasParsed('build-script')) { 41 | throw ArgumentError( 42 | 'Expected exactly one of `--graph-file` or `--build-script`.'); 43 | } 44 | 45 | var assetGraphFile = File(_findAssetGraph(results)); 46 | if (!assetGraphFile.existsSync()) { 47 | throw ArgumentError('Unable to find AssetGraph.'); 48 | } 49 | stdout.writeln('Loading asset graph at ${assetGraphFile.path}...'); 50 | 51 | assetGraph = AssetGraph.deserialize(assetGraphFile.readAsBytesSync()); 52 | packageGraph = await PackageGraph.forThisPackage(); 53 | 54 | var commandRunner = CommandRunner( 55 | '', 'A tool for inspecting the AssetGraph for your build') 56 | ..addCommand(InspectNodeCommand()) 57 | ..addCommand(GraphCommand()) 58 | ..addCommand(QuitCommand()); 59 | 60 | stdout.writeln('Ready, please type in a command:'); 61 | 62 | var shouldExit = false; 63 | while (!shouldExit) { 64 | stdout 65 | ..writeln('') 66 | ..write('> '); 67 | var nextCommand = stdin.readLineSync(); 68 | stdout.writeln(''); 69 | try { 70 | shouldExit = await commandRunner.run(nextCommand!.split(' ')) ?? true; 71 | } on UsageException { 72 | stdout.writeln('Unrecognized option'); 73 | await commandRunner.run(['help']); 74 | } 75 | } 76 | await logSubscription.cancel(); 77 | } 78 | 79 | String _findAssetGraph(ArgResults results) { 80 | if (results.wasParsed('graph-file')) return results['graph-file'] as String; 81 | final scriptPath = results['build-script'] as String; 82 | final scriptFile = File(scriptPath); 83 | if (!scriptFile.existsSync()) { 84 | throw ArgumentError( 85 | 'Expected a build script at $scriptPath but didn\'t find one.'); 86 | } 87 | return assetGraphPathFor(p.url.joinAll(p.split(scriptPath))); 88 | } 89 | 90 | class InspectNodeCommand extends Command { 91 | @override 92 | String get name => 'inspect'; 93 | 94 | @override 95 | String get description => 96 | 'Lists all the information about an asset using a relative or package: uri'; 97 | 98 | @override 99 | String get invocation => '${super.invocation} '; 100 | 101 | InspectNodeCommand() { 102 | argParser.addFlag('verbose', abbr: 'v'); 103 | } 104 | 105 | @override 106 | bool run() { 107 | var argResults = this.argResults!; 108 | var stringUris = argResults.rest; 109 | if (stringUris.isEmpty) { 110 | stderr.writeln('Expected at least one uri for a node to inspect.'); 111 | } 112 | for (var stringUri in stringUris) { 113 | var id = _idFromString(stringUri); 114 | if (id == null) { 115 | continue; 116 | } 117 | var node = assetGraph.get(id); 118 | if (node == null) { 119 | stderr.writeln('Unable to find an asset node for $stringUri.'); 120 | continue; 121 | } 122 | 123 | var description = StringBuffer() 124 | ..writeln('Asset: $stringUri') 125 | ..writeln(' type: ${node.runtimeType}'); 126 | 127 | if (node is GeneratedAssetNode) { 128 | description 129 | ..writeln(' state: ${node.state}') 130 | ..writeln(' wasOutput: ${node.wasOutput}') 131 | ..writeln(' phase: ${node.phaseNumber}') 132 | ..writeln(' isFailure: ${node.isFailure}'); 133 | } 134 | 135 | void _printAsset(AssetId asset) => 136 | _listAsset(asset, description, indentation: ' '); 137 | 138 | if (argResults['verbose'] == true) { 139 | description.writeln(' primary outputs:'); 140 | node.primaryOutputs.forEach(_printAsset); 141 | 142 | description.writeln(' secondary outputs:'); 143 | node.outputs.difference(node.primaryOutputs).forEach(_printAsset); 144 | 145 | if (node is NodeWithInputs) { 146 | description.writeln(' inputs:'); 147 | assetGraph.allNodes 148 | .where((n) => n.outputs.contains(node.id)) 149 | .map((n) => n.id) 150 | .forEach(_printAsset); 151 | } 152 | } 153 | 154 | stdout.write(description); 155 | } 156 | return false; 157 | } 158 | } 159 | 160 | class GraphCommand extends Command { 161 | @override 162 | String get name => 'graph'; 163 | 164 | @override 165 | String get description => 'Lists all the nodes in the graph.'; 166 | 167 | @override 168 | String get invocation => '${super.invocation} '; 169 | 170 | GraphCommand() { 171 | argParser 172 | ..addFlag('generated', 173 | abbr: 'g', help: 'Show only generated assets.', defaultsTo: false) 174 | ..addFlag('original', 175 | abbr: 'o', 176 | help: 'Show only original source assets.', 177 | defaultsTo: false) 178 | ..addOption('package', 179 | abbr: 'p', help: 'Filters nodes to a certain package') 180 | ..addOption('pattern', abbr: 'm', help: 'glob pattern for path matching'); 181 | } 182 | 183 | @override 184 | bool run() { 185 | var argResults = this.argResults!; 186 | var showGenerated = argResults['generated'] as bool; 187 | var showSources = argResults['original'] as bool; 188 | Iterable assets; 189 | if (showGenerated) { 190 | assets = assetGraph.outputs; 191 | } else if (showSources) { 192 | assets = assetGraph.sources; 193 | } else { 194 | assets = assetGraph.allNodes.map((n) => n.id); 195 | } 196 | 197 | var package = argResults['package'] as String?; 198 | if (package != null) { 199 | assets = assets.where((id) => id.package == package); 200 | } 201 | 202 | var pattern = argResults['pattern'] as String?; 203 | if (pattern != null) { 204 | var glob = Glob(pattern); 205 | assets = assets.where((id) => glob.matches(id.path)); 206 | } 207 | 208 | for (var id in assets) { 209 | _listAsset(id, stdout, indentation: ' '); 210 | } 211 | return false; 212 | } 213 | } 214 | 215 | class QuitCommand extends Command { 216 | @override 217 | String get name => 'quit'; 218 | 219 | @override 220 | String get description => 'Exit the inspector'; 221 | 222 | @override 223 | bool run() => true; 224 | } 225 | 226 | AssetId? _idFromString(String stringUri) { 227 | var uri = Uri.parse(stringUri); 228 | if (uri.scheme == 'package') { 229 | return AssetId(uri.pathSegments.first, 230 | p.url.join('lib', p.url.joinAll(uri.pathSegments.skip(1)))); 231 | } else if (!uri.isAbsolute && (uri.scheme == '' || uri.scheme == 'file')) { 232 | return AssetId(packageGraph.root.name, uri.path); 233 | } else { 234 | stderr.writeln('Unrecognized uri $uri, must be a package: uri or a ' 235 | 'relative path.'); 236 | return null; 237 | } 238 | } 239 | 240 | void _listAsset(AssetId output, StringSink buffer, 241 | {String indentation = ' '}) { 242 | var outputUri = output.uri; 243 | if (outputUri.scheme == 'package') { 244 | buffer.writeln('$indentation${output.uri}'); 245 | } else { 246 | buffer.writeln('$indentation${output.path}'); 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /bin/json_model.dart: -------------------------------------------------------------------------------- 1 | import 'dart:collection'; 2 | import 'dart:convert'; 3 | import 'dart:io'; 4 | import 'package:args/args.dart'; 5 | import 'package:path/path.dart' as path; 6 | import 'build_runner.dart' as br; 7 | 8 | // Dart file template 9 | const tpl = 10 | "import 'package:json_annotation/json_annotation.dart';\n%t\npart '%s.g.dart';\n\n@JsonSerializable()\nclass %s {\n %s();\n\n %s\n factory %s.fromJson(Map json) => _\$%sFromJson(json);\n Map toJson() => _\$%sToJson(this);\n}\n"; 11 | 12 | void main(List args) { 13 | String? src; 14 | String? dist; 15 | String? tag; 16 | bool nullable = false; 17 | bool clean = false; 18 | final parser = ArgParser(); 19 | parser.addOption( 20 | 'src', 21 | defaultsTo: './Jsons', 22 | callback: (v) => src = v, 23 | help: "Specify the Json directory.", 24 | ); 25 | parser.addOption( 26 | 'dist', 27 | defaultsTo: 'lib/models', 28 | callback: (v) => dist = v, 29 | help: "Specify the dist directory.", 30 | ); 31 | 32 | parser.addOption( 33 | 'tag', 34 | defaultsTo: '\$', 35 | callback: (v) => tag = v, 36 | help: "Specify the tag ", 37 | ); 38 | 39 | parser.addFlag('nullable', callback: (v) => nullable = v); 40 | parser.addFlag('clean', callback: (v) => clean = v); 41 | 42 | parser.parse(args); 43 | 44 | if (clean) { 45 | br.run(['clean']); 46 | } else if (generateModelClass(src!, dist!, tag!, nullable: nullable)) { 47 | br.run(['build', '--delete-conflicting-outputs']); 48 | } 49 | } 50 | 51 | bool generateModelClass( 52 | String srcDir, 53 | String distDir, 54 | String tag, { 55 | required bool nullable, 56 | }) { 57 | const metaTag = "@meta"; 58 | if (srcDir.endsWith("/")) srcDir = srcDir.substring(0, srcDir.length - 1); 59 | if (distDir.endsWith("/")) distDir = distDir.substring(0, distDir.length - 1); 60 | 61 | final src = Directory(srcDir); 62 | final fileList = src.listSync(recursive: true); 63 | String indexFile = ""; 64 | if (fileList.isEmpty) return false; 65 | if (!Directory(distDir).existsSync()) { 66 | Directory(distDir).createSync(recursive: true); 67 | } 68 | 69 | File file; 70 | 71 | fileList.forEach((f) { 72 | if (FileSystemEntity.isFileSync(f.path)) { 73 | file = File(f.path); 74 | final paths = path.basename(f.path).split("."); 75 | final String fileName = paths.first; 76 | if (paths.last.toLowerCase() != "json" || fileName.startsWith("_")) 77 | return; 78 | 79 | final dartFilePath = f.path 80 | .replaceFirst(srcDir, distDir) 81 | .replaceFirst(RegExp('.json', caseSensitive: false), ".dart"); 82 | 83 | final map = json.decode(file.readAsStringSync()) as Map; 84 | 85 | // To ensure that import statements are not repeated, 86 | // we use Set to save import statements 87 | final importSet = Set(); 88 | 89 | //Create a case-insensitive Map for the meta data of Json file 90 | final meta = LinkedHashMap( 91 | equals: (a, b) => a.toLowerCase().trim() == b.toLowerCase().trim(), 92 | hashCode: (k) => k.toLowerCase().hashCode, 93 | ); 94 | 95 | // Get the meta data of Json file 96 | if (map[metaTag] != null) { 97 | meta.addAll(map[metaTag] as Map); 98 | map.remove(metaTag); 99 | } 100 | 101 | //generated class name 102 | String? className = meta['className'] as String?; 103 | if (className == null || className.isEmpty) { 104 | className = fileName[0].toUpperCase() + fileName.substring(1); 105 | } 106 | 107 | //set ignore 108 | final bool ignore = (meta['ignore'] ?? false) as bool; 109 | if (ignore) { 110 | print('skip: ${f.path}'); 111 | indexFile = exportIndexFile(dartFilePath, distDir, indexFile); 112 | return; 113 | } 114 | 115 | //handle imports 116 | final List imports = (meta['import'] ?? []) as List; 117 | imports.forEach((e) => importSet.add("import '$e'")); 118 | 119 | //set nullable 120 | final bool _nullable = (meta['nullable'] ?? nullable) as bool; 121 | 122 | // comments for Json fields 123 | final comments = meta['comments'] ?? {}; 124 | 125 | // Handle fields in Json file 126 | final StringBuffer fields = StringBuffer(); 127 | map.forEach((key, v) { 128 | key = key.trim(); 129 | if (key.startsWith("_")) return; 130 | if (key.startsWith("@")) { 131 | if (comments[v] != null) { 132 | _writeComments(comments[v],fields); 133 | } 134 | fields.write(key); 135 | fields.write(" "); 136 | fields.write(v); 137 | fields.writeln(";"); 138 | } else { 139 | final bool optionalField = key.endsWith('?'); 140 | final bool notNull = key.endsWith('!'); 141 | if (optionalField || notNull) { 142 | key = key.substring(0, key.length - 1); 143 | } 144 | final bool shouldAppendOptionalFlag = 145 | !notNull && (optionalField || _nullable); 146 | 147 | if (comments[key] != null) { 148 | _writeComments(comments[key],fields); 149 | } 150 | if (!shouldAppendOptionalFlag) { 151 | fields.write('late '); 152 | } 153 | fields.write(getDataType(v, importSet, fileName, tag)); 154 | if (shouldAppendOptionalFlag) { 155 | fields.write('?'); 156 | } 157 | fields.write(" "); 158 | fields.write(key); 159 | //new line 160 | fields.writeln(";"); 161 | } 162 | //indent 163 | fields.write(" "); 164 | }); 165 | 166 | var dist = replaceTemplate(tpl, [ 167 | fileName, 168 | className, 169 | className, 170 | fields.toString(), 171 | className, 172 | className, 173 | className 174 | ]); 175 | // Insert the imports at the head of dart file. 176 | var _import = importSet.join(";\r\n"); 177 | _import += _import.isEmpty ? "" : ";"; 178 | dist = dist.replaceFirst("%t", _import); 179 | //Create dart file 180 | File(dartFilePath) 181 | ..createSync(recursive: true) 182 | ..writeAsStringSync(dist); 183 | indexFile = exportIndexFile(dartFilePath, distDir, indexFile); 184 | print('done: ${f.path} -> $dartFilePath'); 185 | } 186 | }); 187 | if (indexFile.isNotEmpty) { 188 | final p = path.join(distDir, "index.dart"); 189 | File(p).writeAsStringSync(indexFile); 190 | print('create index file: $p'); 191 | } 192 | return indexFile.isNotEmpty; 193 | } 194 | 195 | _writeComments(dynamic comments,StringBuffer sb){ 196 | final arr='$comments'.replaceAll('\r', '').split('\n'); 197 | arr.forEach((element) { 198 | sb.writeln('// $element'); 199 | sb.write(' '); 200 | }); 201 | } 202 | 203 | String exportIndexFile(String p, String distDir, String indexFile) { 204 | final relative = p.replaceFirst(distDir + path.separator, ""); 205 | indexFile += "export '$relative' ; \n"; 206 | return indexFile; 207 | } 208 | 209 | String changeFirstChar(String str, [bool upper = true]) { 210 | return (upper ? str[0].toUpperCase() : str[0].toLowerCase()) + 211 | str.substring(1); 212 | } 213 | 214 | bool isBuiltInType(String type) { 215 | return ['int', 'num', 'string', 'double', 'map', 'list'].contains(type); 216 | } 217 | 218 | String getDataType(v, Set set, String current, String tag) { 219 | current = current.toLowerCase(); 220 | if (v is bool) { 221 | return "bool"; 222 | } else if (v is num) { 223 | return "num"; 224 | } else if (v is Map) { 225 | return "Map"; 226 | } else if (v is List) { 227 | return "List"; 228 | } else if (v is String) { 229 | // handle other type that is not built-in 230 | if (v.startsWith("$tag[]")) { 231 | final type = changeFirstChar(v.substring(3), false); 232 | if (type.toLowerCase() != current && !isBuiltInType(type)) { 233 | set.add('import "$type.dart"'); 234 | } 235 | return "List<${changeFirstChar(type)}>"; 236 | } else if (v.startsWith(tag)) { 237 | final fileName = changeFirstChar(v.substring(1), false); 238 | if (fileName.toLowerCase() != current) { 239 | set.add('import "$fileName.dart"'); 240 | } 241 | return changeFirstChar(fileName); 242 | } else if (v.startsWith("@")) { 243 | return v; 244 | } 245 | return "String"; 246 | } else { 247 | return "String"; 248 | } 249 | } 250 | 251 | String replaceTemplate(String template, List params) { 252 | int matchIndex = 0; 253 | String replace(Match m) { 254 | if (matchIndex < params.length) { 255 | switch (m[0]) { 256 | case "%s": 257 | return params[matchIndex++].toString(); 258 | } 259 | } else { 260 | throw Exception("Missing parameter for string format"); 261 | } 262 | throw Exception("Invalid format string: " + m[0].toString()); 263 | } 264 | 265 | return template.replaceAllMapped("%s", replace); 266 | } 267 | -------------------------------------------------------------------------------- /bin/src/commands/clean.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:args/args.dart'; 8 | import 'package:args/command_runner.dart'; 9 | import 'package:build_runner/src/build_script_generate/build_script_generate.dart'; 10 | import 'package:build_runner/src/entrypoint/base_command.dart' show lineLength; 11 | import 'package:build_runner/src/entrypoint/clean.dart' show cleanFor; 12 | import 'package:build_runner_core/build_runner_core.dart'; 13 | import 'package:logging/logging.dart'; 14 | 15 | class CleanCommand extends Command { 16 | @override 17 | final argParser = ArgParser(usageLineLength: lineLength); 18 | 19 | @override 20 | String get name => 'clean'; 21 | final logger = Logger('clean'); 22 | 23 | @override 24 | String get description => 25 | 'Cleans up output from previous builds. Does not clean up --output ' 26 | 'directories.'; 27 | 28 | @override 29 | Future run() async { 30 | await cleanFor(assetGraphPathFor(scriptLocation), logger); 31 | return 0; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /bin/src/commands/generate_build_script.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:io'; 7 | 8 | import 'package:args/args.dart'; 9 | import 'package:args/command_runner.dart'; 10 | import 'package:build_runner/src/build_script_generate/build_script_generate.dart'; 11 | import 'package:build_runner/src/entrypoint/base_command.dart' show lineLength; 12 | import 'package:logging/logging.dart'; 13 | import 'package:path/path.dart' as p; 14 | 15 | class GenerateBuildScript extends Command { 16 | @override 17 | final argParser = ArgParser(usageLineLength: lineLength); 18 | 19 | @override 20 | String get description => 21 | 'Generate a script to run builds and print the file path ' 22 | 'with no other logging. Useful for wrapping builds with other tools.'; 23 | 24 | @override 25 | String get name => 'generate-build-script'; 26 | 27 | @override 28 | bool get hidden => true; 29 | 30 | @override 31 | Future run() async { 32 | Logger.root.clearListeners(); 33 | var buildScript = await generateBuildScript(); 34 | File(scriptLocation) 35 | ..createSync(recursive: true) 36 | ..writeAsStringSync(buildScript); 37 | print(p.absolute(scriptLocation)); 38 | return 0; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # Run Example 2 | 3 | 1. `cd example` 4 | 2. Run `flutter packages pub run json_model` (in Flutter) or `pub run json_model` (in Dart VM) -------------------------------------------------------------------------------- /example/jsons/card.json: -------------------------------------------------------------------------------- 1 | { 2 | "no":"28838388383756", 3 | "name":"xx bank" 4 | } -------------------------------------------------------------------------------- /example/jsons/test_dir/profile.json: -------------------------------------------------------------------------------- 1 | { 2 | "name":"wendux", 3 | "age":20, 4 | "male":true 5 | } -------------------------------------------------------------------------------- /example/jsons/user.json: -------------------------------------------------------------------------------- 1 | { 2 | "@meta": { 3 | "import": [ 4 | "test_dir/profile.dart" 5 | ], 6 | "comments": { 7 | "name": "名字" 8 | }, 9 | "nullable": false, 10 | "ignore": false 11 | }, 12 | "@JsonKey(ignore: true) Profile?": "profile", 13 | "@JsonKey(name: '+1') int?": "loved", 14 | "name": "wendux", 15 | "father": "$user", 16 | "friends": "$[]user", 17 | "keywords": "$[]String", 18 | "age?": 20 19 | } -------------------------------------------------------------------------------- /example/lib/demo.dart: -------------------------------------------------------------------------------- 1 | //import 'package:json_model/json_model.dart'; 2 | import 'models/index.dart'; 3 | 4 | void main() { 5 | var u = User.fromJson({"name": "Jack", "age": 16, "+1": 20}); 6 | print(u.loved); 7 | } 8 | -------------------------------------------------------------------------------- /example/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.dartlang.org" 9 | source: hosted 10 | version: "23.0.0" 11 | analyzer: 12 | dependency: transitive 13 | description: 14 | name: analyzer 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "2.0.0" 18 | args: 19 | dependency: transitive 20 | description: 21 | name: args 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "2.1.1" 25 | async: 26 | dependency: transitive 27 | description: 28 | name: async 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "2.7.0" 32 | build: 33 | dependency: transitive 34 | description: 35 | name: build 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "2.0.3" 39 | build_config: 40 | dependency: transitive 41 | description: 42 | name: build_config 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.0.0" 46 | build_daemon: 47 | dependency: transitive 48 | description: 49 | name: build_daemon 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "3.0.0" 53 | build_resolvers: 54 | dependency: transitive 55 | description: 56 | name: build_resolvers 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "2.0.4" 60 | build_runner: 61 | dependency: transitive 62 | description: 63 | name: build_runner 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "2.0.6" 67 | build_runner_core: 68 | dependency: transitive 69 | description: 70 | name: build_runner_core 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "7.0.1" 74 | built_collection: 75 | dependency: transitive 76 | description: 77 | name: built_collection 78 | url: "https://pub.dartlang.org" 79 | source: hosted 80 | version: "5.1.0" 81 | built_value: 82 | dependency: transitive 83 | description: 84 | name: built_value 85 | url: "https://pub.dartlang.org" 86 | source: hosted 87 | version: "8.1.1" 88 | charcode: 89 | dependency: transitive 90 | description: 91 | name: charcode 92 | url: "https://pub.dartlang.org" 93 | source: hosted 94 | version: "1.3.1" 95 | checked_yaml: 96 | dependency: transitive 97 | description: 98 | name: checked_yaml 99 | url: "https://pub.dartlang.org" 100 | source: hosted 101 | version: "2.0.1" 102 | cli_util: 103 | dependency: transitive 104 | description: 105 | name: cli_util 106 | url: "https://pub.dartlang.org" 107 | source: hosted 108 | version: "0.3.3" 109 | code_builder: 110 | dependency: transitive 111 | description: 112 | name: code_builder 113 | url: "https://pub.dartlang.org" 114 | source: hosted 115 | version: "4.0.0" 116 | collection: 117 | dependency: transitive 118 | description: 119 | name: collection 120 | url: "https://pub.dartlang.org" 121 | source: hosted 122 | version: "1.15.0" 123 | convert: 124 | dependency: transitive 125 | description: 126 | name: convert 127 | url: "https://pub.dartlang.org" 128 | source: hosted 129 | version: "3.0.1" 130 | crypto: 131 | dependency: transitive 132 | description: 133 | name: crypto 134 | url: "https://pub.dartlang.org" 135 | source: hosted 136 | version: "3.0.1" 137 | dart_style: 138 | dependency: transitive 139 | description: 140 | name: dart_style 141 | url: "https://pub.dartlang.org" 142 | source: hosted 143 | version: "2.0.2" 144 | file: 145 | dependency: transitive 146 | description: 147 | name: file 148 | url: "https://pub.dartlang.org" 149 | source: hosted 150 | version: "6.1.2" 151 | fixnum: 152 | dependency: transitive 153 | description: 154 | name: fixnum 155 | url: "https://pub.dartlang.org" 156 | source: hosted 157 | version: "1.0.0" 158 | frontend_server_client: 159 | dependency: transitive 160 | description: 161 | name: frontend_server_client 162 | url: "https://pub.dartlang.org" 163 | source: hosted 164 | version: "2.1.0" 165 | glob: 166 | dependency: transitive 167 | description: 168 | name: glob 169 | url: "https://pub.dartlang.org" 170 | source: hosted 171 | version: "2.0.1" 172 | graphs: 173 | dependency: transitive 174 | description: 175 | name: graphs 176 | url: "https://pub.dartlang.org" 177 | source: hosted 178 | version: "2.0.0" 179 | http_multi_server: 180 | dependency: transitive 181 | description: 182 | name: http_multi_server 183 | url: "https://pub.dartlang.org" 184 | source: hosted 185 | version: "3.0.1" 186 | http_parser: 187 | dependency: transitive 188 | description: 189 | name: http_parser 190 | url: "https://pub.dartlang.org" 191 | source: hosted 192 | version: "4.0.0" 193 | io: 194 | dependency: transitive 195 | description: 196 | name: io 197 | url: "https://pub.dartlang.org" 198 | source: hosted 199 | version: "1.0.3" 200 | js: 201 | dependency: transitive 202 | description: 203 | name: js 204 | url: "https://pub.dartlang.org" 205 | source: hosted 206 | version: "0.6.3" 207 | json_annotation: 208 | dependency: transitive 209 | description: 210 | name: json_annotation 211 | url: "https://pub.dartlang.org" 212 | source: hosted 213 | version: "4.1.0" 214 | json_model: 215 | dependency: "direct dev" 216 | description: 217 | path: ".." 218 | relative: true 219 | source: path 220 | version: "2.0.0-beta1" 221 | json_serializable: 222 | dependency: "direct dev" 223 | description: 224 | name: json_serializable 225 | url: "https://pub.dartlang.org" 226 | source: hosted 227 | version: "5.0.0" 228 | logging: 229 | dependency: transitive 230 | description: 231 | name: logging 232 | url: "https://pub.dartlang.org" 233 | source: hosted 234 | version: "1.0.1" 235 | matcher: 236 | dependency: transitive 237 | description: 238 | name: matcher 239 | url: "https://pub.dartlang.org" 240 | source: hosted 241 | version: "0.12.10" 242 | meta: 243 | dependency: transitive 244 | description: 245 | name: meta 246 | url: "https://pub.dartlang.org" 247 | source: hosted 248 | version: "1.7.0" 249 | mime: 250 | dependency: transitive 251 | description: 252 | name: mime 253 | url: "https://pub.dartlang.org" 254 | source: hosted 255 | version: "1.0.0" 256 | package_config: 257 | dependency: transitive 258 | description: 259 | name: package_config 260 | url: "https://pub.dartlang.org" 261 | source: hosted 262 | version: "2.0.0" 263 | path: 264 | dependency: transitive 265 | description: 266 | name: path 267 | url: "https://pub.dartlang.org" 268 | source: hosted 269 | version: "1.8.0" 270 | pedantic: 271 | dependency: transitive 272 | description: 273 | name: pedantic 274 | url: "https://pub.dartlang.org" 275 | source: hosted 276 | version: "1.11.1" 277 | pool: 278 | dependency: transitive 279 | description: 280 | name: pool 281 | url: "https://pub.dartlang.org" 282 | source: hosted 283 | version: "1.5.0" 284 | pub_semver: 285 | dependency: transitive 286 | description: 287 | name: pub_semver 288 | url: "https://pub.dartlang.org" 289 | source: hosted 290 | version: "2.0.0" 291 | pubspec_parse: 292 | dependency: transitive 293 | description: 294 | name: pubspec_parse 295 | url: "https://pub.dartlang.org" 296 | source: hosted 297 | version: "1.0.0" 298 | shelf: 299 | dependency: transitive 300 | description: 301 | name: shelf 302 | url: "https://pub.dartlang.org" 303 | source: hosted 304 | version: "1.2.0" 305 | shelf_web_socket: 306 | dependency: transitive 307 | description: 308 | name: shelf_web_socket 309 | url: "https://pub.dartlang.org" 310 | source: hosted 311 | version: "1.0.1" 312 | source_gen: 313 | dependency: transitive 314 | description: 315 | name: source_gen 316 | url: "https://pub.dartlang.org" 317 | source: hosted 318 | version: "1.0.3" 319 | source_helper: 320 | dependency: transitive 321 | description: 322 | name: source_helper 323 | url: "https://pub.dartlang.org" 324 | source: hosted 325 | version: "1.2.0" 326 | source_span: 327 | dependency: transitive 328 | description: 329 | name: source_span 330 | url: "https://pub.dartlang.org" 331 | source: hosted 332 | version: "1.8.1" 333 | stack_trace: 334 | dependency: transitive 335 | description: 336 | name: stack_trace 337 | url: "https://pub.dartlang.org" 338 | source: hosted 339 | version: "1.10.0" 340 | stream_channel: 341 | dependency: transitive 342 | description: 343 | name: stream_channel 344 | url: "https://pub.dartlang.org" 345 | source: hosted 346 | version: "2.1.0" 347 | stream_transform: 348 | dependency: transitive 349 | description: 350 | name: stream_transform 351 | url: "https://pub.dartlang.org" 352 | source: hosted 353 | version: "2.0.0" 354 | string_scanner: 355 | dependency: transitive 356 | description: 357 | name: string_scanner 358 | url: "https://pub.dartlang.org" 359 | source: hosted 360 | version: "1.1.0" 361 | term_glyph: 362 | dependency: transitive 363 | description: 364 | name: term_glyph 365 | url: "https://pub.dartlang.org" 366 | source: hosted 367 | version: "1.2.0" 368 | timing: 369 | dependency: transitive 370 | description: 371 | name: timing 372 | url: "https://pub.dartlang.org" 373 | source: hosted 374 | version: "1.0.0" 375 | typed_data: 376 | dependency: transitive 377 | description: 378 | name: typed_data 379 | url: "https://pub.dartlang.org" 380 | source: hosted 381 | version: "1.3.0" 382 | watcher: 383 | dependency: transitive 384 | description: 385 | name: watcher 386 | url: "https://pub.dartlang.org" 387 | source: hosted 388 | version: "1.0.0" 389 | web_socket_channel: 390 | dependency: transitive 391 | description: 392 | name: web_socket_channel 393 | url: "https://pub.dartlang.org" 394 | source: hosted 395 | version: "2.1.0" 396 | yaml: 397 | dependency: transitive 398 | description: 399 | name: yaml 400 | url: "https://pub.dartlang.org" 401 | source: hosted 402 | version: "3.1.0" 403 | sdks: 404 | dart: ">=2.14.0 <3.0.0" 405 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: example 2 | description: Gernerate model class from Json files. 3 | version: 0.0.1 4 | 5 | environment: 6 | sdk: ">=2.14.0 <3.0.0" 7 | 8 | dev_dependencies: 9 | json_model: 10 | path: ../ 11 | json_serializable: ^5.0.0 12 | -------------------------------------------------------------------------------- /lib/build_runner.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:io'; 7 | 8 | import 'package:args/args.dart'; 9 | import 'package:args/command_runner.dart'; 10 | import 'package:build_runner/src/build_script_generate/bootstrap.dart'; 11 | import 'package:build_runner/src/entrypoint/options.dart'; 12 | import 'package:build_runner/src/entrypoint/runner.dart'; 13 | import 'package:build_runner/src/logging/std_io_logging.dart'; 14 | import 'package:build_runner_core/build_runner_core.dart'; 15 | import 'package:io/ansi.dart'; 16 | import 'package:io/io.dart'; 17 | import 'package:logging/logging.dart'; 18 | 19 | import 'src/commands/clean.dart'; 20 | import 'src/commands/generate_build_script.dart'; 21 | 22 | Future run(List args) async { 23 | // Use the actual command runner to parse the args and immediately print the 24 | // usage information if there is no command provided or the help command was 25 | // explicitly invoked. 26 | var commandRunner = 27 | BuildCommandRunner([], await PackageGraph.forThisPackage()); 28 | var localCommands = [CleanCommand(), GenerateBuildScript()]; 29 | var localCommandNames = localCommands.map((c) => c.name).toSet(); 30 | for (var command in localCommands) { 31 | commandRunner.addCommand(command); 32 | // This flag is added to each command individually and not the top level. 33 | command.argParser.addFlag(verboseOption, 34 | abbr: 'v', 35 | defaultsTo: false, 36 | negatable: false, 37 | help: 'Enables verbose logging.'); 38 | } 39 | 40 | ArgResults parsedArgs; 41 | try { 42 | parsedArgs = commandRunner.parse(args); 43 | } on UsageException catch (e) { 44 | print(red.wrap(e.message)); 45 | print(''); 46 | print(e.usage); 47 | exitCode = ExitCode.usage.code; 48 | return; 49 | } 50 | 51 | var commandName = parsedArgs.command?.name; 52 | 53 | if (parsedArgs.rest.isNotEmpty) { 54 | print( 55 | yellow.wrap('Could not find a command named "${parsedArgs.rest[0]}".')); 56 | print(''); 57 | print(commandRunner.usageWithoutDescription); 58 | exitCode = ExitCode.usage.code; 59 | return; 60 | } 61 | 62 | if (commandName == 'help' || 63 | parsedArgs.wasParsed('help') || 64 | (parsedArgs.command?.wasParsed('help') ?? false)) { 65 | await commandRunner.runCommand(parsedArgs); 66 | return; 67 | } 68 | 69 | if (commandName == null) { 70 | commandRunner.printUsage(); 71 | exitCode = ExitCode.usage.code; 72 | return; 73 | } 74 | 75 | StreamSubscription logListener; 76 | if (commandName == 'daemon') { 77 | // Simple logs only in daemon mode. These get converted into info or 78 | // severe logs by the client. 79 | logListener = Logger.root.onRecord.listen((record) { 80 | if (record.level >= Level.SEVERE) { 81 | var buffer = StringBuffer(record.message); 82 | if (record.error != null) buffer.writeln(record.error); 83 | if (record.stackTrace != null) buffer.writeln(record.stackTrace); 84 | stderr.writeln(buffer); 85 | } else { 86 | stdout.writeln(record.message); 87 | } 88 | }); 89 | } else { 90 | var verbose = parsedArgs.command!['verbose'] as bool? ?? false; 91 | if (verbose) Logger.root.level = Level.ALL; 92 | logListener = 93 | Logger.root.onRecord.listen(stdIOLogListener(verbose: verbose)); 94 | } 95 | if (localCommandNames.contains(commandName)) { 96 | exitCode = await commandRunner.runCommand(parsedArgs) ?? 1; 97 | } else { 98 | while ((exitCode = await generateAndRun(args)) == ExitCode.tempFail.code) {} 99 | } 100 | await logListener.cancel(); 101 | } 102 | -------------------------------------------------------------------------------- /lib/graph_inspector.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:io'; 7 | 8 | import 'package:args/args.dart'; 9 | import 'package:args/command_runner.dart'; 10 | import 'package:build/build.dart'; 11 | import 'package:build_runner_core/build_runner_core.dart'; 12 | import 'package:build_runner_core/src/asset_graph/graph.dart'; 13 | import 'package:build_runner_core/src/asset_graph/node.dart'; 14 | import 'package:glob/glob.dart'; 15 | import 'package:logging/logging.dart'; 16 | import 'package:path/path.dart' as p; 17 | 18 | late final AssetGraph assetGraph; 19 | late final PackageGraph packageGraph; 20 | 21 | final logger = Logger('graph_inspector'); 22 | 23 | Future main(List args) async { 24 | final logSubscription = 25 | Logger.root.onRecord.listen((record) => print(record.message)); 26 | logger.warning( 27 | 'Warning: this tool is unsupported and usage may change at any time, ' 28 | 'use at your own risk.'); 29 | 30 | final argParser = ArgParser() 31 | ..addOption('graph-file', 32 | abbr: 'g', help: 'Specify the asset_graph.json file to inspect.') 33 | ..addOption('build-script', 34 | abbr: 'b', 35 | help: 'Specify the build script to find the asset graph for.', 36 | defaultsTo: '.dart_tool/build/entrypoint/build.dart'); 37 | 38 | final results = argParser.parse(args); 39 | 40 | if (results.wasParsed('graph-file') && results.wasParsed('build-script')) { 41 | throw ArgumentError( 42 | 'Expected exactly one of `--graph-file` or `--build-script`.'); 43 | } 44 | 45 | var assetGraphFile = File(_findAssetGraph(results)); 46 | if (!assetGraphFile.existsSync()) { 47 | throw ArgumentError('Unable to find AssetGraph.'); 48 | } 49 | stdout.writeln('Loading asset graph at ${assetGraphFile.path}...'); 50 | 51 | assetGraph = AssetGraph.deserialize(assetGraphFile.readAsBytesSync()); 52 | packageGraph = await PackageGraph.forThisPackage(); 53 | 54 | var commandRunner = CommandRunner( 55 | '', 'A tool for inspecting the AssetGraph for your build') 56 | ..addCommand(InspectNodeCommand()) 57 | ..addCommand(GraphCommand()) 58 | ..addCommand(QuitCommand()); 59 | 60 | stdout.writeln('Ready, please type in a command:'); 61 | 62 | var shouldExit = false; 63 | while (!shouldExit) { 64 | stdout 65 | ..writeln('') 66 | ..write('> '); 67 | var nextCommand = stdin.readLineSync(); 68 | stdout.writeln(''); 69 | try { 70 | shouldExit = await commandRunner.run(nextCommand!.split(' ')) ?? true; 71 | } on UsageException { 72 | stdout.writeln('Unrecognized option'); 73 | await commandRunner.run(['help']); 74 | } 75 | } 76 | await logSubscription.cancel(); 77 | } 78 | 79 | String _findAssetGraph(ArgResults results) { 80 | if (results.wasParsed('graph-file')) return results['graph-file'] as String; 81 | final scriptPath = results['build-script'] as String; 82 | final scriptFile = File(scriptPath); 83 | if (!scriptFile.existsSync()) { 84 | throw ArgumentError( 85 | 'Expected a build script at $scriptPath but didn\'t find one.'); 86 | } 87 | return assetGraphPathFor(p.url.joinAll(p.split(scriptPath))); 88 | } 89 | 90 | class InspectNodeCommand extends Command { 91 | @override 92 | String get name => 'inspect'; 93 | 94 | @override 95 | String get description => 96 | 'Lists all the information about an asset using a relative or package: uri'; 97 | 98 | @override 99 | String get invocation => '${super.invocation} '; 100 | 101 | InspectNodeCommand() { 102 | argParser.addFlag('verbose', abbr: 'v'); 103 | } 104 | 105 | @override 106 | bool run() { 107 | var argResults = this.argResults!; 108 | var stringUris = argResults.rest; 109 | if (stringUris.isEmpty) { 110 | stderr.writeln('Expected at least one uri for a node to inspect.'); 111 | } 112 | for (var stringUri in stringUris) { 113 | var id = _idFromString(stringUri); 114 | if (id == null) { 115 | continue; 116 | } 117 | var node = assetGraph.get(id); 118 | if (node == null) { 119 | stderr.writeln('Unable to find an asset node for $stringUri.'); 120 | continue; 121 | } 122 | 123 | var description = StringBuffer() 124 | ..writeln('Asset: $stringUri') 125 | ..writeln(' type: ${node.runtimeType}'); 126 | 127 | if (node is GeneratedAssetNode) { 128 | description 129 | ..writeln(' state: ${node.state}') 130 | ..writeln(' wasOutput: ${node.wasOutput}') 131 | ..writeln(' phase: ${node.phaseNumber}') 132 | ..writeln(' isFailure: ${node.isFailure}'); 133 | } 134 | 135 | void _printAsset(AssetId asset) => 136 | _listAsset(asset, description, indentation: ' '); 137 | 138 | if (argResults['verbose'] == true) { 139 | description.writeln(' primary outputs:'); 140 | node.primaryOutputs.forEach(_printAsset); 141 | 142 | description.writeln(' secondary outputs:'); 143 | node.outputs.difference(node.primaryOutputs).forEach(_printAsset); 144 | 145 | if (node is NodeWithInputs) { 146 | description.writeln(' inputs:'); 147 | assetGraph.allNodes 148 | .where((n) => n.outputs.contains(node.id)) 149 | .map((n) => n.id) 150 | .forEach(_printAsset); 151 | } 152 | } 153 | 154 | stdout.write(description); 155 | } 156 | return false; 157 | } 158 | } 159 | 160 | class GraphCommand extends Command { 161 | @override 162 | String get name => 'graph'; 163 | 164 | @override 165 | String get description => 'Lists all the nodes in the graph.'; 166 | 167 | @override 168 | String get invocation => '${super.invocation} '; 169 | 170 | GraphCommand() { 171 | argParser 172 | ..addFlag('generated', 173 | abbr: 'g', help: 'Show only generated assets.', defaultsTo: false) 174 | ..addFlag('original', 175 | abbr: 'o', 176 | help: 'Show only original source assets.', 177 | defaultsTo: false) 178 | ..addOption('package', 179 | abbr: 'p', help: 'Filters nodes to a certain package') 180 | ..addOption('pattern', abbr: 'm', help: 'glob pattern for path matching'); 181 | } 182 | 183 | @override 184 | bool run() { 185 | var argResults = this.argResults!; 186 | var showGenerated = argResults['generated'] as bool; 187 | var showSources = argResults['original'] as bool; 188 | Iterable assets; 189 | if (showGenerated) { 190 | assets = assetGraph.outputs; 191 | } else if (showSources) { 192 | assets = assetGraph.sources; 193 | } else { 194 | assets = assetGraph.allNodes.map((n) => n.id); 195 | } 196 | 197 | var package = argResults['package'] as String?; 198 | if (package != null) { 199 | assets = assets.where((id) => id.package == package); 200 | } 201 | 202 | var pattern = argResults['pattern'] as String?; 203 | if (pattern != null) { 204 | var glob = Glob(pattern); 205 | assets = assets.where((id) => glob.matches(id.path)); 206 | } 207 | 208 | for (var id in assets) { 209 | _listAsset(id, stdout, indentation: ' '); 210 | } 211 | return false; 212 | } 213 | } 214 | 215 | class QuitCommand extends Command { 216 | @override 217 | String get name => 'quit'; 218 | 219 | @override 220 | String get description => 'Exit the inspector'; 221 | 222 | @override 223 | bool run() => true; 224 | } 225 | 226 | AssetId? _idFromString(String stringUri) { 227 | var uri = Uri.parse(stringUri); 228 | if (uri.scheme == 'package') { 229 | return AssetId(uri.pathSegments.first, 230 | p.url.join('lib', p.url.joinAll(uri.pathSegments.skip(1)))); 231 | } else if (!uri.isAbsolute && (uri.scheme == '' || uri.scheme == 'file')) { 232 | return AssetId(packageGraph.root.name, uri.path); 233 | } else { 234 | stderr.writeln('Unrecognized uri $uri, must be a package: uri or a ' 235 | 'relative path.'); 236 | return null; 237 | } 238 | } 239 | 240 | void _listAsset(AssetId output, StringSink buffer, 241 | {String indentation = ' '}) { 242 | var outputUri = output.uri; 243 | if (outputUri.scheme == 'package') { 244 | buffer.writeln('$indentation${output.uri}'); 245 | } else { 246 | buffer.writeln('$indentation${output.path}'); 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /lib/index.dart: -------------------------------------------------------------------------------- 1 | import 'dart:collection'; 2 | import 'dart:convert'; 3 | import 'dart:io'; 4 | import 'package:args/args.dart'; 5 | import 'package:path/path.dart' as path; 6 | import 'build_runner.dart' as br; 7 | 8 | // Dart file template 9 | const tpl = 10 | "import 'package:json_annotation/json_annotation.dart';\n%t\npart '%s.g.dart';\n\n@JsonSerializable()\nclass %s {\n %s();\n\n %s\n factory %s.fromJson(Map json) => _\$%sFromJson(json);\n Map toJson() => _\$%sToJson(this);\n}\n"; 11 | 12 | void run(List args) { 13 | String? src; 14 | String? dist; 15 | String? tag; 16 | bool nullable = false; 17 | bool clean = false; 18 | final parser = ArgParser(); 19 | parser.addOption( 20 | 'src', 21 | defaultsTo: './Jsons', 22 | callback: (v) => src = v, 23 | help: "Specify the Json directory.", 24 | ); 25 | parser.addOption( 26 | 'dist', 27 | defaultsTo: 'lib/models', 28 | callback: (v) => dist = v, 29 | help: "Specify the dist directory.", 30 | ); 31 | 32 | parser.addOption( 33 | 'tag', 34 | defaultsTo: '\$', 35 | callback: (v) => tag = v, 36 | help: "Specify the tag ", 37 | ); 38 | 39 | parser.addFlag('nullable', callback: (v) => nullable = v); 40 | parser.addFlag('clean', callback: (v) => clean = v); 41 | 42 | parser.parse(args); 43 | 44 | if (clean) { 45 | br.run(['clean']); 46 | } else if (generateModelClass(src!, dist!, tag!, nullable: nullable)) { 47 | br.run(['build', '--delete-conflicting-outputs']); 48 | } 49 | } 50 | 51 | bool generateModelClass( 52 | String srcDir, 53 | String distDir, 54 | String tag, { 55 | required bool nullable, 56 | }) { 57 | const metaTag = "@meta"; 58 | if (srcDir.endsWith("/")) srcDir = srcDir.substring(0, srcDir.length - 1); 59 | if (distDir.endsWith("/")) distDir = distDir.substring(0, distDir.length - 1); 60 | 61 | final src = Directory(srcDir); 62 | final fileList = src.listSync(recursive: true); 63 | String indexFile = ""; 64 | if (fileList.isEmpty) return false; 65 | if (!Directory(distDir).existsSync()) { 66 | Directory(distDir).createSync(recursive: true); 67 | } 68 | 69 | File file; 70 | 71 | fileList.forEach((f) { 72 | if (FileSystemEntity.isFileSync(f.path)) { 73 | file = File(f.path); 74 | final paths = path.basename(f.path).split("."); 75 | final String fileName = paths.first; 76 | if (paths.last.toLowerCase() != "json" || fileName.startsWith("_")) 77 | return; 78 | 79 | final dartFilePath = f.path 80 | .replaceFirst(srcDir, distDir) 81 | .replaceFirst(RegExp('.json', caseSensitive: false), ".dart"); 82 | 83 | final map = json.decode(file.readAsStringSync()) as Map; 84 | 85 | // To ensure that import statements are not repeated, 86 | // we use Set to save import statements 87 | final importSet = Set(); 88 | 89 | //Create a case-insensitive Map for the meta data of Json file 90 | final meta = LinkedHashMap( 91 | equals: (a, b) => a.toLowerCase().trim() == b.toLowerCase().trim(), 92 | hashCode: (k) => k.toLowerCase().hashCode, 93 | ); 94 | 95 | // Get the meta data of Json file 96 | if (map[metaTag] != null) { 97 | meta.addAll(map[metaTag] as Map); 98 | map.remove(metaTag); 99 | } 100 | 101 | //generated class name 102 | String? className = meta['className'] as String?; 103 | if (className == null || className.isEmpty) { 104 | className = fileName[0].toUpperCase() + fileName.substring(1); 105 | } 106 | 107 | //set ignore 108 | final bool ignore = (meta['ignore'] ?? false) as bool; 109 | if (ignore) { 110 | print('skip: ${f.path}'); 111 | indexFile = exportIndexFile(dartFilePath, distDir, indexFile); 112 | return; 113 | } 114 | 115 | //handle imports 116 | final List imports = (meta['import'] ?? []) as List; 117 | imports.forEach((e) => importSet.add("import '$e'")); 118 | 119 | //set nullable 120 | final bool _nullable = (meta['nullable'] ?? nullable) as bool; 121 | 122 | // comments for Json fields 123 | final comments = meta['comments'] ?? {}; 124 | 125 | // Handle fields in Json file 126 | final StringBuffer fields = StringBuffer(); 127 | map.forEach((key, v) { 128 | key = key.trim(); 129 | if (key.startsWith("_")) return; 130 | if (key.startsWith("@")) { 131 | if (comments[v] != null) { 132 | _writeComments(comments[v],fields); 133 | } 134 | fields.write(key); 135 | fields.write(" "); 136 | fields.write(v); 137 | fields.writeln(";"); 138 | } else { 139 | final bool optionalField = key.endsWith('?'); 140 | final bool notNull = key.endsWith('!'); 141 | if (optionalField || notNull) { 142 | key = key.substring(0, key.length - 1); 143 | } 144 | final bool shouldAppendOptionalFlag = 145 | !notNull && (optionalField || _nullable); 146 | 147 | if (comments[key] != null) { 148 | _writeComments(comments[key],fields); 149 | } 150 | if (!shouldAppendOptionalFlag) { 151 | fields.write('late '); 152 | } 153 | fields.write(getDataType(v, importSet, fileName, tag)); 154 | if (shouldAppendOptionalFlag) { 155 | fields.write('?'); 156 | } 157 | fields.write(" "); 158 | fields.write(key); 159 | //new line 160 | fields.writeln(";"); 161 | } 162 | //indent 163 | fields.write(" "); 164 | }); 165 | 166 | var dist = replaceTemplate(tpl, [ 167 | fileName, 168 | className, 169 | className, 170 | fields.toString(), 171 | className, 172 | className, 173 | className 174 | ]); 175 | // Insert the imports at the head of dart file. 176 | var _import = importSet.join(";\r\n"); 177 | _import += _import.isEmpty ? "" : ";"; 178 | dist = dist.replaceFirst("%t", _import); 179 | //Create dart file 180 | File(dartFilePath) 181 | ..createSync(recursive: true) 182 | ..writeAsStringSync(dist); 183 | indexFile = exportIndexFile(dartFilePath, distDir, indexFile); 184 | print('done: ${f.path} -> $dartFilePath'); 185 | } 186 | }); 187 | if (indexFile.isNotEmpty) { 188 | final p = path.join(distDir, "index.dart"); 189 | File(p).writeAsStringSync(indexFile); 190 | print('create index file: $p'); 191 | } 192 | return indexFile.isNotEmpty; 193 | } 194 | 195 | _writeComments(dynamic comments,StringBuffer sb){ 196 | final arr='$comments'.replaceAll('\r', '').split('\n'); 197 | arr.forEach((element) { 198 | sb.writeln('// $element'); 199 | sb.write(' '); 200 | }); 201 | } 202 | 203 | String exportIndexFile(String p, String distDir, String indexFile) { 204 | final relative = p.replaceFirst(distDir + path.separator, ""); 205 | indexFile += "export '$relative' ; \n"; 206 | return indexFile; 207 | } 208 | 209 | String changeFirstChar(String str, [bool upper = true]) { 210 | return (upper ? str[0].toUpperCase() : str[0].toLowerCase()) + 211 | str.substring(1); 212 | } 213 | 214 | bool isBuiltInType(String type) { 215 | return ['int', 'num', 'string', 'double', 'map', 'list'].contains(type); 216 | } 217 | 218 | String getDataType(v, Set set, String current, String tag) { 219 | current = current.toLowerCase(); 220 | if (v is bool) { 221 | return "bool"; 222 | } else if (v is num) { 223 | return "num"; 224 | } else if (v is Map) { 225 | return "Map"; 226 | } else if (v is List) { 227 | return "List"; 228 | } else if (v is String) { 229 | // handle other type that is not built-in 230 | if (v.startsWith("$tag[]")) { 231 | final type = changeFirstChar(v.substring(3), false); 232 | if (type.toLowerCase() != current && !isBuiltInType(type)) { 233 | set.add('import "$type.dart"'); 234 | } 235 | return "List<${changeFirstChar(type)}>"; 236 | } else if (v.startsWith(tag)) { 237 | final fileName = changeFirstChar(v.substring(1), false); 238 | if (fileName.toLowerCase() != current) { 239 | set.add('import "$fileName.dart"'); 240 | } 241 | return changeFirstChar(fileName); 242 | } else if (v.startsWith("@")) { 243 | return v; 244 | } 245 | return "String"; 246 | } else { 247 | return "String"; 248 | } 249 | } 250 | 251 | String replaceTemplate(String template, List params) { 252 | int matchIndex = 0; 253 | String replace(Match m) { 254 | if (matchIndex < params.length) { 255 | switch (m[0]) { 256 | case "%s": 257 | return params[matchIndex++].toString(); 258 | } 259 | } else { 260 | throw Exception("Missing parameter for string format"); 261 | } 262 | throw Exception("Invalid format string: " + m[0].toString()); 263 | } 264 | 265 | return template.replaceAllMapped("%s", replace); 266 | } 267 | -------------------------------------------------------------------------------- /lib/json_model.dart: -------------------------------------------------------------------------------- 1 | export 'index.dart' show run; 2 | -------------------------------------------------------------------------------- /lib/src/commands/clean.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:args/args.dart'; 8 | import 'package:args/command_runner.dart'; 9 | import 'package:build_runner/src/build_script_generate/build_script_generate.dart'; 10 | import 'package:build_runner/src/entrypoint/base_command.dart' show lineLength; 11 | import 'package:build_runner/src/entrypoint/clean.dart' show cleanFor; 12 | import 'package:build_runner_core/build_runner_core.dart'; 13 | import 'package:logging/logging.dart'; 14 | 15 | class CleanCommand extends Command { 16 | @override 17 | final argParser = ArgParser(usageLineLength: lineLength); 18 | 19 | @override 20 | String get name => 'clean'; 21 | final logger = Logger('clean'); 22 | 23 | @override 24 | String get description => 25 | 'Cleans up output from previous builds. Does not clean up --output ' 26 | 'directories.'; 27 | 28 | @override 29 | Future run() async { 30 | await cleanFor(assetGraphPathFor(scriptLocation), logger); 31 | return 0; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/src/commands/generate_build_script.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:io'; 7 | 8 | import 'package:args/args.dart'; 9 | import 'package:args/command_runner.dart'; 10 | import 'package:build_runner/src/build_script_generate/build_script_generate.dart'; 11 | import 'package:build_runner/src/entrypoint/base_command.dart' show lineLength; 12 | import 'package:logging/logging.dart'; 13 | import 'package:path/path.dart' as p; 14 | 15 | class GenerateBuildScript extends Command { 16 | @override 17 | final argParser = ArgParser(usageLineLength: lineLength); 18 | 19 | @override 20 | String get description => 21 | 'Generate a script to run builds and print the file path ' 22 | 'with no other logging. Useful for wrapping builds with other tools.'; 23 | 24 | @override 25 | String get name => 'generate-build-script'; 26 | 27 | @override 28 | bool get hidden => true; 29 | 30 | @override 31 | Future run() async { 32 | Logger.root.clearListeners(); 33 | var buildScript = await generateBuildScript(); 34 | File(scriptLocation) 35 | ..createSync(recursive: true) 36 | ..writeAsStringSync(buildScript); 37 | print(p.absolute(scriptLocation)); 38 | return 0; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /model.tpl: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | %t 3 | part '%s.g.dart'; 4 | 5 | @JsonSerializable() 6 | class %s { 7 | %s(); 8 | 9 | %s 10 | factory %s.fromJson(Map json) => _$%sFromJson(json); 11 | Map toJson() => _$%sToJson(this); 12 | } 13 | -------------------------------------------------------------------------------- /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.dartlang.org" 9 | source: hosted 10 | version: "26.0.0" 11 | analyzer: 12 | dependency: transitive 13 | description: 14 | name: analyzer 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "2.3.0" 18 | args: 19 | dependency: "direct main" 20 | description: 21 | name: args 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "2.3.0" 25 | async: 26 | dependency: transitive 27 | description: 28 | name: async 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "2.8.2" 32 | boolean_selector: 33 | dependency: transitive 34 | description: 35 | name: boolean_selector 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "2.1.0" 39 | build: 40 | dependency: "direct main" 41 | description: 42 | name: build 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "2.1.0" 46 | build_config: 47 | dependency: transitive 48 | description: 49 | name: build_config 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "1.0.0" 53 | build_daemon: 54 | dependency: transitive 55 | description: 56 | name: build_daemon 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "3.0.0" 60 | build_resolvers: 61 | dependency: transitive 62 | description: 63 | name: build_resolvers 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "2.0.4" 67 | build_runner: 68 | dependency: "direct main" 69 | description: 70 | name: build_runner 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "2.1.2" 74 | build_runner_core: 75 | dependency: "direct main" 76 | description: 77 | name: build_runner_core 78 | url: "https://pub.dartlang.org" 79 | source: hosted 80 | version: "7.1.0" 81 | built_collection: 82 | dependency: transitive 83 | description: 84 | name: built_collection 85 | url: "https://pub.dartlang.org" 86 | source: hosted 87 | version: "5.1.1" 88 | built_value: 89 | dependency: transitive 90 | description: 91 | name: built_value 92 | url: "https://pub.dartlang.org" 93 | source: hosted 94 | version: "8.1.2" 95 | charcode: 96 | dependency: transitive 97 | description: 98 | name: charcode 99 | url: "https://pub.dartlang.org" 100 | source: hosted 101 | version: "1.3.1" 102 | checked_yaml: 103 | dependency: transitive 104 | description: 105 | name: checked_yaml 106 | url: "https://pub.dartlang.org" 107 | source: hosted 108 | version: "2.0.1" 109 | cli_util: 110 | dependency: transitive 111 | description: 112 | name: cli_util 113 | url: "https://pub.dartlang.org" 114 | source: hosted 115 | version: "0.3.3" 116 | code_builder: 117 | dependency: transitive 118 | description: 119 | name: code_builder 120 | url: "https://pub.dartlang.org" 121 | source: hosted 122 | version: "4.1.0" 123 | collection: 124 | dependency: transitive 125 | description: 126 | name: collection 127 | url: "https://pub.dartlang.org" 128 | source: hosted 129 | version: "1.15.0" 130 | convert: 131 | dependency: transitive 132 | description: 133 | name: convert 134 | url: "https://pub.dartlang.org" 135 | source: hosted 136 | version: "3.0.1" 137 | coverage: 138 | dependency: transitive 139 | description: 140 | name: coverage 141 | url: "https://pub.dartlang.org" 142 | source: hosted 143 | version: "1.0.3" 144 | crypto: 145 | dependency: transitive 146 | description: 147 | name: crypto 148 | url: "https://pub.dartlang.org" 149 | source: hosted 150 | version: "3.0.1" 151 | dart_style: 152 | dependency: transitive 153 | description: 154 | name: dart_style 155 | url: "https://pub.dartlang.org" 156 | source: hosted 157 | version: "2.2.0" 158 | file: 159 | dependency: transitive 160 | description: 161 | name: file 162 | url: "https://pub.dartlang.org" 163 | source: hosted 164 | version: "6.1.2" 165 | fixnum: 166 | dependency: transitive 167 | description: 168 | name: fixnum 169 | url: "https://pub.dartlang.org" 170 | source: hosted 171 | version: "1.0.0" 172 | frontend_server_client: 173 | dependency: transitive 174 | description: 175 | name: frontend_server_client 176 | url: "https://pub.dartlang.org" 177 | source: hosted 178 | version: "2.1.2" 179 | glob: 180 | dependency: "direct main" 181 | description: 182 | name: glob 183 | url: "https://pub.dartlang.org" 184 | source: hosted 185 | version: "2.0.1" 186 | graphs: 187 | dependency: transitive 188 | description: 189 | name: graphs 190 | url: "https://pub.dartlang.org" 191 | source: hosted 192 | version: "2.1.0" 193 | http_multi_server: 194 | dependency: transitive 195 | description: 196 | name: http_multi_server 197 | url: "https://pub.dartlang.org" 198 | source: hosted 199 | version: "3.0.1" 200 | http_parser: 201 | dependency: transitive 202 | description: 203 | name: http_parser 204 | url: "https://pub.dartlang.org" 205 | source: hosted 206 | version: "4.0.0" 207 | io: 208 | dependency: "direct main" 209 | description: 210 | name: io 211 | url: "https://pub.dartlang.org" 212 | source: hosted 213 | version: "1.0.3" 214 | js: 215 | dependency: transitive 216 | description: 217 | name: js 218 | url: "https://pub.dartlang.org" 219 | source: hosted 220 | version: "0.6.3" 221 | json_annotation: 222 | dependency: transitive 223 | description: 224 | name: json_annotation 225 | url: "https://pub.dartlang.org" 226 | source: hosted 227 | version: "4.1.0" 228 | lint: 229 | dependency: "direct dev" 230 | description: 231 | name: lint 232 | url: "https://pub.dartlang.org" 233 | source: hosted 234 | version: "1.7.2" 235 | logging: 236 | dependency: "direct main" 237 | description: 238 | name: logging 239 | url: "https://pub.dartlang.org" 240 | source: hosted 241 | version: "1.0.2" 242 | matcher: 243 | dependency: transitive 244 | description: 245 | name: matcher 246 | url: "https://pub.dartlang.org" 247 | source: hosted 248 | version: "0.12.11" 249 | meta: 250 | dependency: transitive 251 | description: 252 | name: meta 253 | url: "https://pub.dartlang.org" 254 | source: hosted 255 | version: "1.7.0" 256 | mime: 257 | dependency: transitive 258 | description: 259 | name: mime 260 | url: "https://pub.dartlang.org" 261 | source: hosted 262 | version: "1.0.0" 263 | node_preamble: 264 | dependency: transitive 265 | description: 266 | name: node_preamble 267 | url: "https://pub.dartlang.org" 268 | source: hosted 269 | version: "2.0.1" 270 | package_config: 271 | dependency: transitive 272 | description: 273 | name: package_config 274 | url: "https://pub.dartlang.org" 275 | source: hosted 276 | version: "2.0.2" 277 | path: 278 | dependency: "direct main" 279 | description: 280 | name: path 281 | url: "https://pub.dartlang.org" 282 | source: hosted 283 | version: "1.8.0" 284 | pedantic: 285 | dependency: transitive 286 | description: 287 | name: pedantic 288 | url: "https://pub.dartlang.org" 289 | source: hosted 290 | version: "1.11.1" 291 | pool: 292 | dependency: transitive 293 | description: 294 | name: pool 295 | url: "https://pub.dartlang.org" 296 | source: hosted 297 | version: "1.5.0" 298 | pub_semver: 299 | dependency: transitive 300 | description: 301 | name: pub_semver 302 | url: "https://pub.dartlang.org" 303 | source: hosted 304 | version: "2.1.0" 305 | pubspec_parse: 306 | dependency: transitive 307 | description: 308 | name: pubspec_parse 309 | url: "https://pub.dartlang.org" 310 | source: hosted 311 | version: "1.1.0" 312 | shelf: 313 | dependency: transitive 314 | description: 315 | name: shelf 316 | url: "https://pub.dartlang.org" 317 | source: hosted 318 | version: "1.2.0" 319 | shelf_packages_handler: 320 | dependency: transitive 321 | description: 322 | name: shelf_packages_handler 323 | url: "https://pub.dartlang.org" 324 | source: hosted 325 | version: "3.0.0" 326 | shelf_static: 327 | dependency: transitive 328 | description: 329 | name: shelf_static 330 | url: "https://pub.dartlang.org" 331 | source: hosted 332 | version: "1.1.0" 333 | shelf_web_socket: 334 | dependency: transitive 335 | description: 336 | name: shelf_web_socket 337 | url: "https://pub.dartlang.org" 338 | source: hosted 339 | version: "1.0.1" 340 | source_map_stack_trace: 341 | dependency: transitive 342 | description: 343 | name: source_map_stack_trace 344 | url: "https://pub.dartlang.org" 345 | source: hosted 346 | version: "2.1.0" 347 | source_maps: 348 | dependency: transitive 349 | description: 350 | name: source_maps 351 | url: "https://pub.dartlang.org" 352 | source: hosted 353 | version: "0.10.10" 354 | source_span: 355 | dependency: transitive 356 | description: 357 | name: source_span 358 | url: "https://pub.dartlang.org" 359 | source: hosted 360 | version: "1.8.1" 361 | stack_trace: 362 | dependency: transitive 363 | description: 364 | name: stack_trace 365 | url: "https://pub.dartlang.org" 366 | source: hosted 367 | version: "1.10.0" 368 | stream_channel: 369 | dependency: transitive 370 | description: 371 | name: stream_channel 372 | url: "https://pub.dartlang.org" 373 | source: hosted 374 | version: "2.1.0" 375 | stream_transform: 376 | dependency: transitive 377 | description: 378 | name: stream_transform 379 | url: "https://pub.dartlang.org" 380 | source: hosted 381 | version: "2.0.0" 382 | string_scanner: 383 | dependency: transitive 384 | description: 385 | name: string_scanner 386 | url: "https://pub.dartlang.org" 387 | source: hosted 388 | version: "1.1.0" 389 | term_glyph: 390 | dependency: transitive 391 | description: 392 | name: term_glyph 393 | url: "https://pub.dartlang.org" 394 | source: hosted 395 | version: "1.2.0" 396 | test: 397 | dependency: "direct dev" 398 | description: 399 | name: test 400 | url: "https://pub.dartlang.org" 401 | source: hosted 402 | version: "1.17.12" 403 | test_api: 404 | dependency: transitive 405 | description: 406 | name: test_api 407 | url: "https://pub.dartlang.org" 408 | source: hosted 409 | version: "0.4.3" 410 | test_core: 411 | dependency: transitive 412 | description: 413 | name: test_core 414 | url: "https://pub.dartlang.org" 415 | source: hosted 416 | version: "0.4.2" 417 | timing: 418 | dependency: transitive 419 | description: 420 | name: timing 421 | url: "https://pub.dartlang.org" 422 | source: hosted 423 | version: "1.0.0" 424 | typed_data: 425 | dependency: transitive 426 | description: 427 | name: typed_data 428 | url: "https://pub.dartlang.org" 429 | source: hosted 430 | version: "1.3.0" 431 | vm_service: 432 | dependency: transitive 433 | description: 434 | name: vm_service 435 | url: "https://pub.dartlang.org" 436 | source: hosted 437 | version: "7.3.0" 438 | watcher: 439 | dependency: transitive 440 | description: 441 | name: watcher 442 | url: "https://pub.dartlang.org" 443 | source: hosted 444 | version: "1.0.0" 445 | web_socket_channel: 446 | dependency: transitive 447 | description: 448 | name: web_socket_channel 449 | url: "https://pub.dartlang.org" 450 | source: hosted 451 | version: "2.1.0" 452 | webkit_inspection_protocol: 453 | dependency: transitive 454 | description: 455 | name: webkit_inspection_protocol 456 | url: "https://pub.dartlang.org" 457 | source: hosted 458 | version: "1.0.0" 459 | yaml: 460 | dependency: transitive 461 | description: 462 | name: yaml 463 | url: "https://pub.dartlang.org" 464 | source: hosted 465 | version: "3.1.0" 466 | sdks: 467 | dart: ">=2.14.0 <3.0.0" 468 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: json_model 2 | description: Gernerating Dart model class from Json files with one command. 3 | version: 1.0.0 4 | homepage: https://github.com/flutterchina/json_model 5 | repository: https://github.com/flutterchina/json_model 6 | 7 | environment: 8 | sdk: ">=2.14.0 <3.0.0" 9 | 10 | dependencies: 11 | args: ^2.1.1 12 | build: ^2.0.3 13 | glob: ^2.0.1 14 | logging: ^1.0.1 15 | io: ^1.0.3 16 | build_runner_core: ^7.0.0 17 | build_runner: ^2.0.0 18 | path: ^1.8.0 19 | dev_dependencies: 20 | test: ^1.5.1 21 | lint: ^1.0.0 22 | -------------------------------------------------------------------------------- /test/json_model_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | 3 | void main() { 4 | test('adds one to input values', () { 5 | }); 6 | } 7 | --------------------------------------------------------------------------------