跳到主要内容

JSON 转 Dart 类:Flutter 模型不用再手写 fromJson

把接口返回的 JSON 直接转成 Flutter 模型类,自动生成 final 字段、fromJson 工厂构造、toJson 和空安全可空类型,嵌套对象和对象数组也一并处理,省掉手写 model 的重复劳动。

发布于 作者 李雷
#Flutter #Dart #JSON #序列化 #模型生成

JSON 转 Dart 类:Flutter 模型不用再手写 fromJson

写 Flutter 的人都知道,接口数据反序列化是最枯燥的环节之一。后端给你一段 JSON,你要照着字段名一个一个敲 final、敲构造函数、敲 fromJson 里的 json['xxx'] as String,再把 toJson 反向写一遍。字段稍微多一点,或者嵌套两三层,光是抄写就要十几分钟,还容易把 int 写成 double、把可空字段漏掉 ?

这篇讲讲怎么用 JSON 转 Dart 类 把这段重复劳动直接省掉:粘 JSON,拿到一组能直接放进项目的模型类。

它到底生成什么

每个 JSON 对象会变成一个 Dart 类,带这几样东西:

  • final 强类型字段,默认不可变,符合 Flutter 推荐的状态写法
  • 命名构造函数,非空字段带 required
  • 一个 factory 类名.fromJson(Map<String, dynamic> json) 工厂构造
  • 一个 Map<String, dynamic> toJson()

JSON 的 key 会被转成合法的 camelCase Dart 标识符,site_admin 变成 siteAdmin,created-at 变成 createdAt。但 fromJsontoJson 读写的仍然是原始线上 key,所以模型往返不会丢字段,你也不用为了改名再写一个自定义 converter。

fromJson 工厂构造怎么来的

很多人第一次接触 Dart 序列化会卡在 factory 上。手写时这一段长这样:

factory User.fromJson(Map<String, dynamic> json) {
  return User(
    id: json['id'] as int,
    name: json['name'] as String,
  );
}

工具会把这段完整内联写出来,文件本身就能编译,不依赖任何代码生成。如果你的项目已经在跑 build_runner,打开 json_serializable 选项,它就改成生成 @JsonSerializable() 注解、part 'user.g.dart'; 指令、改名字段的 @JsonKey(name:),以及 _$UserFromJson / _$UserToJson 桥接,你跑一次 dart run build_runner build 即可。两种风格随你切。

一段真实的 JSON 转成 Dart

拿一个常见的接口响应来看。输入:

{
  "user_id": 7,
  "name": "Li Lei",
  "is_active": true,
  "score": 4.5,
  "address": { "city": "Beijing", "zip": "100000" }
}

输出:

class Root {
  final int userId;
  final String name;
  final bool isActive;
  final double score;
  final RootAddress address;

  Root({
    required this.userId,
    required this.name,
    required this.isActive,
    required this.score,
    required this.address,
  });

  factory Root.fromJson(Map<String, dynamic> json) {
    return Root(
      userId: json['user_id'] as int,
      name: json['name'] as String,
      isActive: json['is_active'] as bool,
      score: (json['score'] as num).toDouble(),
      address: RootAddress.fromJson(json['address'] as Map<String, dynamic>),
    );
  }

  Map<String, dynamic> toJson() => {
    'user_id': userId,
    'name': name,
    'is_active': isActive,
    'score': score,
    'address': address.toJson(),
  };
}

注意 address 这个嵌套对象被抽成了独立的 RootAddress 类,父类的 fromJson 自动递归进去。字段名全部 camelCase 化,而 map 的 key 仍是 user_idis_active 这些线上原名。

空安全和可空类型

空安全默认开启,这是工具最省心的地方。它会采样所有对象元素来判断每个字段:

  • 在每个采样对象里都出现、且从不为 null 的字段,推断成非空类型,构造参数带 required
  • 某些数组元素里缺的 key,或见过 JSON null 的字段,变成可空 T?,构造参数去掉 required
  • 只出现过 null 的值,推断成 dynamic

这正好对上 Firebase、Supabase 这类数据源的现实:不是每个文档或每一行都有每个 key。缺字段时,带 null 守卫的 fromJson(json['x'] == null ? null : ...)不会在解析时抛异常。如果你还在维护 2.12 之前的旧 Dart,也可以关掉空安全,每个参数退回普通可选。

数字类型还有个细节:JSON 只有一种数字,Dart 有 intdouble。当一个字段在某行是整数 0、另一行是小数 4.5 时,工具会放宽到 num,这是同时容纳两者的共同父类型,调用处两种字面量都不用再 cast。

嵌套对象和对象数组

嵌套不止一层时,工具会逐层抽类,User.address 变成 UserAddress,依此类推。对象数组是另一个容易写错的地方:它会把数组里所有元素折叠成一个共享类,而不是生成 N 个几乎一样的类。哪怕列表有 200 项,你也只需要对一个强类型元素模型推理。只在部分元素里出现的 key,会自动变成可空。标量数组则变成 List<T>.from(json['tags'] as List)

我自己的体会

我之前接一个第三方支付回调,字段三十多个,还嵌了两层。手写 model 那天我抄到一半就把 amountdouble 写成了 int,联调时金额永远少了小数,排查了快一个小时才发现是 model 写错。后来改用工具粘一次 JSON,几秒钟出全套类,num 的放宽规则直接帮我躲过了同样的坑。从那以后接新接口我都先转一遍,把生成结果当草稿,再按业务需要微调那几个边界字段,比从零手敲稳得多。

如果你的工具链是 Kotlin 或别的语言,同一套思路也有对应版本,比如 JSON 转 Kotlin 数据类;转之前想先把 JSON 格式校验一下,可以过一遍 JSON 格式化工具

一切都在浏览器里跑,粘进来的 JSON 不会离开标签页,结果可以通过分享链接复现,也能一键复制。下次后端再丢给你一段 JSON,别急着抄,先转一遍。


Made by Toolora · Updated 2026-06-13