Flutter/Dart 没有运行时反射,JSON 序列化需要手动映射或借助代码生成工具。推荐使用 json_serializablefreezed 实现类型安全的序列化。


一、手动序列化

1.1 基本模式

class User {
  final int id;
  final String name;
  final String email;
  final DateTime createdAt;
  final Address? address; // 嵌套对象

  const User({
    required this.id,
    required this.name,
    required this.email,
    required this.createdAt,
    this.address,
  });

  factory User.fromJson(Map<String, dynamic> json) => User(
    id: json['id'] as int,
    name: json['name'] as String,
    email: json['email'] as String,
    createdAt: DateTime.parse(json['created_at'] as String),
    address: json['address'] != null
        ? Address.fromJson(json['address'] as Map<String, dynamic>)
        : null,
  );

  Map<String, dynamic> toJson() => {
    'id': id,
    'name': name,
    'email': email,
    'created_at': createdAt.toIso8601String(),
    if (address != null) 'address': address!.toJson(),
  };
}

缺点:

  • 大量重复代码
  • 容易手误(字段名拼错)
  • 不支持 copyWith==hashCode

二、json_serializable(自动代码生成)

dependencies:
  json_annotation: ^4.8.1

dev_dependencies:
  json_serializable: ^6.7.1
  build_runner: ^2.4.0

2.1 定义模型

import 'package:json_annotation/json_annotation.dart';

part 'product.g.dart'; // 生成文件

(
  explicitToJson: true,     // 嵌套对象也调用 toJson
  fieldRename: FieldRename.snake,  // 自动 camelCase → snake_case
)
class Product {
  final int id;
  final String name;
  final double price;

  (name: 'image_urls') // 自定义字段名映射
  final List<String> imageUrls;

  (fromJson: _dateFromJson, toJson: _dateToJson)
  final DateTime publishedAt;

  (defaultValue: true) // 字段缺失时使用默认值
  final bool isActive;

  (includeFromJson: false, includeToJson: false)
  final String? localNote; // 本地字段,不参与序列化

  const Product({
    required this.id,
    required this.name,
    required this.price,
    required this.imageUrls,
    required this.publishedAt,
    this.isActive = true,
    this.localNote,
  });

  // 生成的工厂方法
  factory Product.fromJson(Map<String, dynamic> json) =>
      _$ProductFromJson(json);

  Map<String, dynamic> toJson() => _$ProductToJson(this);

  // 自定义日期解析
  static DateTime _dateFromJson(String date) => DateTime.parse(date);
  static String _dateToJson(DateTime date) => date.toIso8601String();
}

2.2 运行代码生成

# 一次性生成
dart run build_runner build --delete-conflicting-outputs

# 监听模式(开发时推荐)
dart run build_runner watch --delete-conflicting-outputs

三、freezed(不可变模型 + 联合类型)

freezed 是功能最强的模型生成库,支持不可变数据类、copyWith、union 类型。

dependencies:
  freezed_annotation: ^2.4.1
  json_annotation: ^4.8.1

dev_dependencies:
  freezed: ^2.4.7
  json_serializable: ^6.7.1
  build_runner: ^2.4.0

3.1 不可变数据类

import 'package:freezed_annotation/freezed_annotation.dart';

part 'user.freezed.dart';
part 'user.g.dart';


class User with _$User {
  const factory User({
    required int id,
    required String name,
    required String email,
    ([]) List<String> roles,
    (false) bool isAdmin,
    String? avatarUrl,
    (name: 'created_at') required DateTime createdAt,
  }) = _User;

  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
}

// freezed 自动生成:
// ✅ copyWith
// ✅ == / hashCode
// ✅ toString
// ✅ fromJson / toJson

// 使用 copyWith
final updatedUser = user.copyWith(name: 'New Name', isAdmin: true);

3.2 Union 类型(密封类/Result 模式)


class ApiResult<T> with _$ApiResult<T> {
  const factory ApiResult.loading() = _Loading;
  const factory ApiResult.success(T data) = _Success;
  const factory ApiResult.error(String message, {int? code}) = _Error;
}

// 使用 when 处理所有情况
Widget buildFromResult(ApiResult<List<Product>> result) {
  return result.when(
    loading: () => const CircularProgressIndicator(),
    success: (products) => ProductGrid(products: products),
    error: (message, code) => ErrorWidget(message: message),
  );
}

// maybeWhen:只处理部分情况
result.maybeWhen(
  success: (data) => showData(data),
  orElse: () => showPlaceholder(),
)

// map:转换
final displayString = result.map(
  loading: (_) => 'Loading...',
  success: (s) => '${s.data.length} items',
  error: (e) => 'Error: ${e.message}',
);

四、手动 vs 自动对比

功能手动json_serializablefreezed
JSON 序列化✅(生成)✅(生成)
copyWith❌ 需手写✅ 自动
== / hashCode❌ 需手写✅ 自动
toString❌ 需手写✅ 自动
Union 类型
不可变性手动 const手动 final强制不可变
适用场景极简模型中型项目中大型项目

五、API 响应统一包装


class ApiResponse<T> with _$ApiResponse<T> {
  const factory ApiResponse({
    required int code,
    required String message,
    T? data,
    ([]) List<ValidationError> errors,
  }) = _ApiResponse;

  factory ApiResponse.fromJson(
    Map<String, dynamic> json,
    T Function(Object?) fromJsonT,
  ) => _$ApiResponseFromJson(json, fromJsonT);
}

// 使用泛型解析
final response = ApiResponse<List<Product>>.fromJson(
  jsonData,
  (json) => (json as List).map((e) => Product.fromJson(e)).toList(),
);

小结

推荐方案选择依据
手动模型极少/临时代码
json_serializable普通数据模型,中型项目
freezed需要不可变、copyWith、Union 类型,推荐大型项目

👉 下一节:5.3 本地存储

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐