- Flutter 功能最全的JsonToDart工具(桌面Web海陆空支持)
- Flutter JsonToDart Mac版 lei了,真的不mark吗
- Flutter go-flutter desktop 填坑
- Flutter JsonToDart 工具
六年,一个工具,一段成长
世界一直在变,人类能从那么多生物中脱颖而出,很大一部分原因就是------我们会用工具。
六年前,我写了一个小工具,叫 Json to Dart 。当时只是想省点手动写 model
的功夫,没想到这一做就是六年。
这六年里,从微软的 Silverlight
、WPF
、UWP
,到谷歌的 Flutter
;从熟悉的 C#
转向 Dart
;还记得最开始写这个工具的时候,Dart
还是 2.x
版本,后来 Dart
推出了空安全(null safety
)等特性,我也陆续做了适配和重构,一路跟着 Dart
语言演进,工具也跟着我一起不断成长。
现在,它已经发布了超过 30 个版本,服务了不少开发者。
这个工具,见证了我从 .NET
世界跨入移动开发的整个过程,也陪我经历了无数次写完就想重构
的夜晚。
说到底,它只是个工具,但对我来说,是六年技术旅程的缩影。
希望未来还能继续一起进化。
前言
说到当时做这个工具的目的:
-
老是被服务端坑,各种数据类型瞎返回。
-
参数命名混乱,强迫症根本不能忍。
-
生成
copyWith
, 相等判断(hashCode,==
或者Equatable
) 等常见方法。 -
避免
Dart
关键字,避免类名重复。 -
避免对象数组缺失字段(后端返回对象的字段可能不全)。
-
数组解析避免一个解析错误报错,整个列表报错。
-
统一的代码风格规范。
客户端工具说明
- Web: fluttercandies.github.io/JsonToDart
- Macos/Windows: Releases · fluttercandies/JsonToDart
使用

左边是 json
的输入框,右边是生成的 json
类的结构
格式化
点击格式化按钮,将 json
转换为右边可视化的 json
类结构
更多设置

设置会全部自动保存。
数据类型全方位保护
大家一定会有被服务端坑的时候吧? 不按规定好了的数据类型传值,导致 json
整个解析失败。
打开这个开关,就会在获取数据的时候加一层保护,当然你可以自己定义这个方法,默认代码如下:
dart
T? asT<T extends Object?>(dynamic value, [T? defaultValue]) {
if (value is T) {
return value;
}
try {
if (value != null) {
final String valueS = value.toString();
if (String == T || '' is T) {
return valueS as T;
} else if (int == T || 0 is T) {
return int.parse(valueS) as T;
} else if (double == T || 0.0 is T) {
return double.parse(valueS) as T;
} else if (bool == T || false is T) {
if (valueS == '0' || valueS == '1') {
return (valueS == '1') as T;
}
return (valueS == 'true') as T;
} else {
return FFConvert.convert<T>(value);
}
}
} catch (e, stackTrace) {
log('asT<$T>', error: e, stackTrace: stackTrace);
return defaultValue;
}
return defaultValue;
}
class FFConvert {
FFConvert._();
static T? Function<T extends Object?>(dynamic value) convert = <T>(
dynamic value,
) {
if (value == null) {
return null;
}
return json.decode(value.toString()) as T?;
};
}
说到 asT
,最近还被 ChatGPT
坑了一下。原来类型不一致的时候,我是这样去判断的 0 is T
,这样运行是没有问题的(Dart
混淆了也能通过),但我最近重构的时候又问了下 ChatGPT
, 它给我的答案是 int == T
,但是这个写法有一个问题,那就是如果用户写的是 asT<int?>
的话,int == T
是不通过的。这让我想到一个程序员的梗,运行稳定的代码不要轻易去重构
。
数组全方位保护
在循环数组的时候,一个出错,导致json整个解析失败的情况,大家遇到过吧?
打开这个开关,将对每一次循环解析进行保护,代码如下
dart
void tryCatch(Function f) {
try {
f?.call();
} catch (e, stack) {
debugPrint("$e");
debugPrint("$stack");
}
}
遍历数组次数
在服务器返回的数据中,数组里面不是每一个 item
都带有全部的属性,
如果只检查第一个话,会存在属性丢失的情况
你可以通过多次循环来避免丢失属性
选项有 1,20,99
99
就代表循环全部进行检查
属性命名
属性命名规范选项:
- 保持原样
- 驼峰式命名小驼峰
josnToDart
- 帕斯卡命名大驼峰
JsonToDart
- 匈牙利命名下划线
json_to_dart
Dart 官方推荐 驼峰式命名小驼峰
属性排序
对属性进行排序
排序选项:
- 保持原样
- 升序排列
- 降序排序
添加保护方法
是否添加保护方法。数据类型全方位保护/数组全方位保护 这 2
个开启的时候会生成方法。 第一次使用的时候开启就可以了,你可以方法提出去放一个 dart
文件里面(并且在文件头中加入引用)。 后面生成的时候就没必要再开启了。
文件头部信息
可以在这里添加 copyright
,improt
,创建人信息等等,支持[Date yyyy MM-dd]
来生成时间,Date
后面为日期格式。
比如 [Date yyyy MM-dd]
会将你生成 Dart
代码的时间按照 yyyy MM-dd
的格式生成对应时间
修改json类信息

点击格式化之后,右边会显示可视化的json类结构。
第一列是在 json
中对应的 key
第二列为属性类型/类的名字。如果是类名,会用蓝色箭头表示
第三列是属性的名字,输入选项如果为空,会报红提示
第四列是属性的访问器类型
生成Dart

做好设置之后,点击生成,就会自动将生成的代码复制到裁剪板,当然你也可以从弹框中复制。
纯 Dart
的 JsonToDart
库
客户端工具虽然功能丰富,但是没法做更精细的自定义。 我们需要做得更多,比如可以根据项目的设计,直接读取后端的文档,生成数据模型和接口请求方法。
为此,我将 JsonToDart
的解析部分抽离成单独的纯 Dart
库 (fluttercandies/json_to_dart_library: json_to_dart_library)。
我们可以通过自定义配置控制器和代码生成过程,来自定义属于自己的 JsonToDart
。
dart
import 'dart:io';
import 'package:json_to_dart_library/json_to_dart_library.dart';
Future<void> main(List<String> args) async {
registerConfig(MyJsonToDartConfig());
registerController(MyJsonToDartController());
DartObject? dartObject = await jsonToDartController.jsonToDartObject(
json: '''{"data":[{"a":1}],"msg":"s","code":0}''',
);
var errors = jsonToDartController.getErrors();
if (errors.isNotEmpty) {
print('Errors found:');
for (var error in errors) {
print(error);
}
return;
}
if (dartObject != null) {
var dartCode = jsonToDartController.generateDartCode(dartObject);
File('output.dart').writeAsStringSync(dartCode!);
print('Dart code generated successfully:');
}
}
class MyJsonToDartConfig extends JsonToDartConfig {
@override
bool get addMethod => true;
@override
bool get enableArrayProtection => true;
@override
bool get enableDataProtection => true;
@override
int get traverseArrayCount => 99;
@override
bool get nullable => true;
@override
bool get nullsafety => true;
// @override
// bool get smartNullable => true;
@override
DartObject createDartObject({
required String uid,
required int depth,
required MapEntry<String, dynamic> keyValuePair,
required bool nullable,
DartObject? dartObject,
}) {
return MyDartObject(
uid: uid,
depth: depth,
keyValuePair: keyValuePair,
nullable: nullable,
dartObject: dartObject,
);
}
@override
DartProperty createProperty({
required String uid,
required int depth,
required MapEntry<String, dynamic> keyValuePair,
required bool nullable,
required DartObject dartObject,
}) {
return MyDartProperty(
uid: uid,
depth: depth,
keyValuePair: keyValuePair,
nullable: nullable,
dartObject: dartObject,
);
}
}
class MyJsonToDartController with JsonToDartControllerMixin {}
class MyDartObject extends DartObject {
MyDartObject({
required super.uid,
required super.depth,
required super.keyValuePair,
required super.nullable,
super.dartObject,
});
@override
String toString() {
// 做一些自己的事情
return super.toString();
}
}
class MyDartProperty extends DartProperty {
MyDartProperty({
required super.uid,
required super.depth,
required super.keyValuePair,
required super.nullable,
required DartObject super.dartObject,
});
}
DartObject/DartProperty
json
会被转换成 DartObject/DartProperty
对象,里面存储着信息,通过DartObject
的 toString
方法 得到整个 json
转换的 dart
数据模型代码。你可以通过重写这 2
个类来修改生成的 dart
数据模型代码,并且在下一步的配置中指定新的 DartObject/DartProperty
对象。
dart
class MyDartObject extends DartObject {
MyDartObject({
required super.uid,
required super.depth,
required super.keyValuePair,
required super.nullable,
super.dartObject,
});
@override
String toString() {
// 做一些自己的事情
return super.toString();
}
}
class MyDartProperty extends DartProperty {
MyDartProperty({
required super.uid,
required super.depth,
required super.keyValuePair,
required super.nullable,
required DartObject super.dartObject,
});
}
配置项说明
你可以通过重载 JsonToDartConfig
来重新设置参数。
- 定义新的配置
dart
class MyJsonToDartConfig extends JsonToDartConfig {
@override
bool get addMethod => true;
}
- 注册新的配置
dart
registerConfig(MyJsonToDartConfig());
通用配置项
参数名 | 类型 | 默认值 | 说明 |
---|---|---|---|
addMethod |
bool |
true |
是否生成 asT 和数组循环保护方法。 |
enableArrayProtection |
bool |
false |
是否在解析数组时启用保护机制 |
enableDataProtection |
bool |
false |
是否在解析数据(如 null 或格式不合法)时启用保护 |
fileHeaderInfo |
String |
'' |
每个生成的 Dart 文件顶部添加的自定义注释头 |
traverseArrayCount |
int |
1 |
检查数组时遍历的元素数量 |
propertyNamingConventionsType |
PropertyNamingConventionsType |
camelCase |
属性命名规范,例如 camelCase、snake_case 等 |
propertyAccessorType |
PropertyAccessorType |
none |
属性访问器风格,如是否使用 getter/setter |
propertyNameSortingType |
PropertyNameSortingType |
none |
属性名称是否进行排序 |
nullsafety |
bool |
true |
是否启用 Dart 的空安全特性 |
nullable |
bool |
true |
属性是否允许为 null |
smartNullable |
bool |
false |
是否启用智能可空推断 |
addCopyMethod |
bool |
false |
是否生成 copyWith 方法 |
automaticCheck |
bool |
true |
是否在生成前自动检测潜在问题 |
showResultDialog |
bool |
true |
是否在转换完成后显示结果弹窗 |
equalityMethodType |
EqualityMethodType |
official |
控制是否生成 == 和 hashCode 或者是 Equatable 形式 |
deepCopy |
bool |
false |
copyWith 方法是否支持对象的深拷贝 |
formatter |
DartFormatter? |
DartFormatter(latestLanguageVersion) |
格式化生成的 Dart 代码,控制缩进、风格等 |
对象创建方法
当你需要重载 DartObject
和 DartProperty
的时候,你需要重载这 2 个方法,返回你自定义的 DartObject
和 DartProperty
方法名 | 返回类型 | 说明 |
---|---|---|
createProperty({...}) |
DartProperty |
创建新的 DartProperty,用于生成属性模型。参数包括 uid 、depth 、keyValuePair 、nullable 、dartObject |
createDartObject({...}) |
DartObject |
创建新的 DartObject,用于生成类结构模型。参数包括 uid 、depth 、keyValuePair 、nullable 、dartObject? |
错误信息与断言
返回的错误信息,你可以在这里支持国际化。
名称 | 类型 | 示例值 / 描述 |
---|---|---|
propertyNameAssert(uid) |
String |
返回属性名为空的提示信息,如:"$uid: property name is empty" |
classNameAssert(uid) |
String |
返回类名为空的提示信息,如:"$uid: class name is empty" |
propertyCantSameAsClassName |
String |
属性的名字不能跟类名一样 'property can\'t the same as Class name' |
keywordCheckFailed(name) |
String |
返回关键词冲突错误信息,如:'name' is a key word!' |
propertyCantSameAsType |
String |
属性的名字不能跟类型一样 'property can\'t the same as Type' |
containsIllegalCharacters |
String |
包含非法的字符 'contains illegal characters' |
duplicateProperties |
String |
有重复的属性 'There are duplicate properties' |
duplicateClasses |
String |
有重复的类 'There are duplicate classes' |
控制器说明
你可以通过混入 JsonToDartControllerMixin
来重新重载方法。
- 定义新的控制器
dart
class MyJsonToDartController with JsonToDartControllerMixin {}
- 注册新的控制器
dart
registerController(MyJsonToDartController());
方法说明
方法名 | 返回类型 | 说明 |
---|---|---|
Future<DartObject?> jsonToDartObject({ required String json, String rootObjectName = 'Root' }) |
Future<DartObject?> |
将 JSON 字符串解析为 DartObject 对象结构,处理空安全和配置项 |
DartObject? dynamicToDartObject(dynamic jsonData, { String rootObjectName = 'Root' }) |
DartObject? |
将动态 JSON 数据(Map/List )转换为 DartObject ,自动适配结构 |
String? generateDartCode(DartObject? dartObject) |
String? |
根据 DartObject 生成 Dart 代码,支持插入 header 、导入语句、空安全等 |
List<String> getErrors() |
List<String> |
收集所有 DartObject 与 DartProperty 中的错误信息列表,用于展示/校验 |
void handleError(Object? e, StackTrace stack) |
void |
错误处理函数,打印错误并抛出异常(支持统一调试) |
成员变量说明
成员名 | 类型 | 说明 |
---|---|---|
Set<DartProperty> allProperties |
Set<DartProperty> |
存储当前解析过程中的所有属性信息,用于去重等判断 |
Set<DartObject> allObjects |
Set<DartObject> |
存储当前解析过程中所有生成的类对象,用于去重等判断 |
Set<DartObject> printedObjects |
Set<DartObject> |
存储已打印输出过的对象,避免重复输出类定义 |
结语
通过将 JsonToDart
的解析部分抽离成单独的纯 Dart
库 (fluttercandies/json_to_dart_library: json_to_dart_library),现在大家都可以依赖这个库去做自己的脚本或者客户端了!
JsonToDart
接下来就靠你自己继续进化了!
工具对人类最大的意义,不在于"让事变得更容易",而在于拓展了我们的能力边界。
- 用石头砸核桃,是早期的"硬件工具";
- 用算盘计算,是认知能力的延伸;
- 用编程语言写出工具,是"思维的工具"。
从打火石、车轮,到现代的 AI、自动化工具,它们共同帮助人类从"靠自己"做事,变成"设计系统"做事,工具在进化,人也在进化!
爱 Flutter
,爱糖果
,欢迎加入Flutter Candies,一起生产可爱的Flutter小糖果
最最后放上 Flutter Candies 全家桶,真香。
