Flutter 深潜:当动态 List 遇上 JSON 序列化,如何优雅解决?

那些年,我们在 toJson 上踩过的坑,以及最终的破局之道

写在前面

在 Flutter 开发中,JSON 序列化是一个绕不开的话题。对于简单的数据结构,json_serializable 可以轻松应对。但当你的数据结构中出现 List 这种动态类型时,一切就变得不那么优雅了。

本文将深入探讨动态 List 的 JSON 序列化问题,并给出多种解决方案,帮助你在不同场景下做出最优选择。

一、问题的起源:为什么 toJson 会失效?

先来看一个典型场景:

dart 复制代码
class Order {
  String orderId;
  List<dynamic> items;  // 可能包含商品、优惠券、备注等多种类型
  
  Order(this.orderId, this.items);
  
  // 直接使用 json_serializable 生成的 toJson 会怎样?
}

当你尝试序列化这个 Order 对象时,可能会遇到以下错误:

复制代码
Converting object to an encodable object failed: 
Instance of 'Product' 

根本原因:Dart 的 jsonEncode 函数只知道如何处理基础类型(String、int、bool、Map、List),当遇到自定义对象时,它不知道该如何处理。

二、解决方案全景图

根据不同的业务复杂度,我们有四种解决方案:

方案一:手动类型映射(适合类型较少的场景)

最直观的方式是在 toJson 中遍历列表,根据运行时类型进行转换:

dart 复制代码
class Order {
  String orderId;
  List<dynamic> items;
  
  Map<String, dynamic> toJson() {
    return {
      'orderId': orderId,
      'items': items.map((item) {
        if (item is Product) {
          return {'type': 'product', 'data': item.toJson()};
        } else if (item is Coupon) {
          return {'type': 'coupon', 'data': item.toJson()};
        } else if (item is String || item is num || item is bool) {
          return item;  // 基础类型直接返回
        } else {
          throw Exception('Unsupported type: ${item.runtimeType}');
        }
      }).toList(),
    };
  }
}

优点:逻辑清晰,易于理解

缺点:每次新增类型都需要修改代码

方案二:接口约束(适合类型可预期的场景)

定义一个统一的序列化接口,所有可序列化的对象都实现它:

dart 复制代码
// 1. 定义接口
abstract class JsonSerializable {
  Map<String, dynamic> toJson();
}

// 2. 子类实现
class Product implements JsonSerializable {
  String name;
  double price;
  
  @override
  Map<String, dynamic> toJson() => {
    'name': name,
    'price': price,
  };
}

// 3. 列表声明为接口类型
class Order {
  String orderId;
  List<JsonSerializable> items;  // 类型约束
  
  Map<String, dynamic> toJson() {
    return {
      'orderId': orderId,
      'items': items.map((item) => item.toJson()).toList(),
    };
  }
}

优点:类型安全,编译期检查

缺点:无法存储基础类型(除非也包装成接口实现)

方案三:json_serializable + 自定义转换(适合大型项目)

利用 @JsonKey 注解的 toJson 和 fromJson 参数:

dart 复制代码
import 'package:json_annotation/json_annotation.dart';

part 'order.g.dart';

@JsonSerializable()
class Order {
  String orderId;
  
  @JsonKey(toJson: _itemsToJson, fromJson: _itemsFromJson)
  List<dynamic> items;
  
  Order(this.orderId, this.items);
  
  factory Order.fromJson(Map<String, dynamic> json) => _$OrderFromJson(json);
  Map<String, dynamic> toJson() => _$OrderToJson(this);
}

// 自定义序列化逻辑
List<dynamic> _itemsToJson(List<dynamic> items) {
  return items.map((item) {
    if (item is Product) {
      return {'__type__': 'Product', ...item.toJson()};
    } else if (item is Coupon) {
      return {'__type__': 'Coupon', ...item.toJson()};
    }
    return item;
  }).toList();
}

List<dynamic> _itemsFromJson(List<dynamic> items) {
  return items.map((item) {
    final type = item['__type__'];
    switch (type) {
      case 'Product':
        return Product.fromJson(item);
      case 'Coupon':
        return Coupon.fromJson(item);
      default:
        return item;
    }
  }).toList();
}

优点:与现有工具链完美集成,支持复杂类型

缺点:需要维护类型标识字段

方案四:递归转换器(适合深度嵌套场景)

当动态 List 中还嵌套着其他 List 或 Map 时,需要一个递归解决方案:

dart 复制代码
dynamic convertToJson(dynamic obj) {
  if (obj == null) return null;
  
  // 处理自定义对象
  if (obj is JsonSerializable) {
    return obj.toJson();
  }
  
  // 递归处理列表
  if (obj is List) {
    return obj.map(convertToJson).toList();
  }
  
  // 递归处理 Map
  if (obj is Map) {
    return obj.map((key, value) => MapEntry(key, convertToJson(value)));
  }
  
  // 基础类型直接返回
  return obj;
}

// 使用示例
class Order {
  String orderId;
  List<dynamic> items;
  
  Map<String, dynamic> toJson() {
    return {
      'orderId': orderId,
      'items': convertToJson(items),
    };
  }
}

优点:能处理任意复杂的嵌套结构

缺点:性能略差,不适合超大列表

三、实战案例:电商订单系统

假设我们正在开发一个电商 App 的订单系统,订单中的 items 可能包含:

· 商品信息(Product)

· 优惠券信息(Coupon)

· 普通文本备注(String)

· 赠品信息(Gift)

来看一个完整的实现:

dart 复制代码
// 基类
abstract class OrderItem {
  String get type;
  Map<String, dynamic> toJson();
}

// 商品类
class Product extends OrderItem {
  @override
  String get type => 'product';
  
  String name;
  int quantity;
  double price;
  
  Product({required this.name, required this.quantity, required this.price});
  
  @override
  Map<String, dynamic> toJson() => {
    'type': type,
    'name': name,
    'quantity': quantity,
    'price': price,
  };
}

// 优惠券类
class Coupon extends OrderItem {
  @override
  String get type => 'coupon';
  
  String code;
  double discount;
  
  Coupon({required this.code, required this.discount});
  
  @override
  Map<String, dynamic> toJson() => {
    'type': type,
    'code': code,
    'discount': discount,
  };
}

// 订单类
class Order {
  String orderId;
  List<OrderItem> items;  // 使用基类类型
  String? remark;  // 备注(普通字符串)
  
  Order({required this.orderId, required this.items, this.remark});
  
  Map<String, dynamic> toJson() {
    return {
      'orderId': orderId,
      'items': items.map((item) => item.toJson()).toList(),
      'remark': remark,
    };
  }
  
  // 反序列化
  factory Order.fromJson(Map<String, dynamic> json) {
    return Order(
      orderId: json['orderId'],
      items: (json['items'] as List).map((item) {
        switch (item['type']) {
          case 'product':
            return Product(
              name: item['name'],
              quantity: item['quantity'],
              price: item['price'],
            );
          case 'coupon':
            return Coupon(
              code: item['code'],
              discount: item['discount'],
            );
          default:
            throw Exception('Unknown item type');
        }
      }).toList(),
      remark: json['remark'],
    );
  }
}

// 使用示例
void main() {
  final order = Order(
    orderId: 'ORD-001',
    items: [
      Product(name: 'iPhone 15', quantity: 1, price: 5999),
      Coupon(code: 'SAVE100', discount: 100),
    ],
    remark: '请在工作日送货',
  );
  
  final json = order.toJson();
  print(jsonEncode(json));
  // 输出:{"orderId":"ORD-001","items":[{"type":"product","name":"iPhone 15","quantity":1,"price":5999},{"type":"coupon","code":"SAVE100","discount":100}],"remark":"请在工作日送货"}
}

四、性能优化建议

  1. 缓存转换结果:如果同一个对象需要多次序列化,考虑缓存其 JSON 表示
  2. 使用 const 构造函数:对于不变的配置类,使用 const 可以减少内存分配
  3. 避免深度递归:对于深度嵌套超过 100 层的结构,考虑迭代方案替代递归

五、总结与选型建议

场景 推荐方案 理由

小型项目,类型固定 方案一(手动映射) 简单直接,无需引入复杂模式

中型项目,类型可控 方案二(接口约束) 类型安全,易于维护

大型项目,需求多变 方案三(json_serializable) 工具链完善,减少样板代码

遗留系统,结构复杂 方案四(递归转换) 兼容性强,适应各种数据结构

写在最后

动态类型的 JSON 序列化是 Flutter 开发中的一个经典难题,但通过合理的设计模式,我们可以优雅地解决它。核心思想是:明确类型边界,统一序列化接口,善用递归处理。

希望这篇文章能帮助你少踩一些坑。如果你有更好的解决方案,欢迎在评论区分享讨论!


相关推荐
恋猫de小郭2 小时前
Flutter 的 build_runner 已经今非昔比,看看 build_runner 2.13 有什么特别?
android·前端·flutter
小白学鸿蒙1 天前
使用Flutter从0到1构建OpenHarmony/HarmonyOS应用
flutter·华为·harmonyos
不爱吃糖的程序媛1 天前
Flutter OH 框架介绍
flutter
ljt27249606611 天前
Flutter笔记--加水印
笔记·flutter
恋猫de小郭1 天前
2026,Android Compose 终于支持 Hot Reload 了,但是收费
android·前端·flutter
ljt27249606612 天前
Flutter笔记--事件处理
笔记·flutter
Feng-licong2 天前
告别手写 UI:当 Google Stitch 遇上 Flutter,2026 年的“Vibe Coding”开发流
flutter·ui
不爱吃糖的程序媛3 天前
Flutter OH Engine构建指导
flutter
小蜜蜂嗡嗡3 天前
flutter实现付费解锁内容的遮挡
android·flutter