如何在运行时切换json_serializable的JsonConverter以兼容DateTime与Firestore Timestamp?
基于
json_serializable实现运行时切换JsonConverter的方案 针对你的需求——日常使用DateTime对象、仅与Firestore交互时切换为Timestamp转换,同时支持跨Flutter/Dart SDK/Firestore环境共享模型,以下是实用的实现方案:
核心思路
通过自定义转换器+动态序列化入口替代built_value的多序列化器方案,让模型类本身持有DateTime字段(保证跨环境通用性),同时提供不同的序列化/反序列化入口,在运行时根据环境选择对应的转换逻辑。
步骤1:定义两种转换器
分别实现普通DateTime与字符串的转换器,以及FirestoreTimestamp与DateTime的转换器:
1.1 普通环境DateTime转换器
// date_time_converter.dart import 'package:json_annotation/json_annotation.dart'; class DateTimeConverter implements JsonConverter<DateTime, String> { const DateTimeConverter(); @override DateTime fromJson(String json) => DateTime.parse(json); @override String toJson(DateTime object) => object.toIso8601String(); }
1.2 Firestore Timestamp转换器
// timestamp_converter.dart import 'package:json_annotation/json_annotation.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; class TimestampConverter implements JsonConverter<DateTime, Timestamp> { const TimestampConverter(); @override DateTime fromJson(Timestamp json) => json.toDate(); @override Timestamp toJson(DateTime object) => Timestamp.fromDate(object); }
步骤2:实现支持动态切换的模型类
以FooBar模型为例,通过多入口方法或全局转换器提供者实现运行时切换:
方式一:多入口方法(推荐,无全局状态)
直接为模型类编写不同环境的序列化/反序列化入口,逻辑清晰无副作用:
// foo_bar.dart import 'package:json_annotation/json_annotation.dart'; import 'date_time_converter.dart'; // 条件导入:仅Firestore环境引入Timestamp转换器,避免非Firestore环境依赖cloud_firestore import 'timestamp_converter.dart' if (dart.library.html) 'date_time_converter.dart'; part 'foo_bar.g.dart'; @JsonSerializable() class FooBar { final String id; final DateTime createdAt; FooBar({required this.id, required this.createdAt}); // 普通Dart/Flutter环境入口:使用DateTimeConverter factory FooBar.fromJson(Map<String, dynamic> json) { final processedJson = Map<String, dynamic>.from(json); if (processedJson.containsKey('createdAt')) { processedJson['createdAt'] = const DateTimeConverter().fromJson(processedJson['createdAt'] as String); } return _$FooBarFromJson(processedJson); } Map<String, dynamic> toJson() { final json = _$FooBarToJson(this); if (json.containsKey('createdAt')) { json['createdAt'] = const DateTimeConverter().toJson(json['createdAt'] as DateTime); } return json; } // Firestore环境入口:使用TimestampConverter factory FooBar.fromFirestore(Map<String, dynamic> json) { final processedJson = Map<String, dynamic>.from(json); if (processedJson.containsKey('createdAt')) { processedJson['createdAt'] = const TimestampConverter().fromJson(processedJson['createdAt'] as Timestamp); } return _$FooBarFromJson(processedJson); } Map<String, dynamic> toFirestore() { final json = _$FooBarToJson(this); if (json.containsKey('createdAt')) { json['createdAt'] = const TimestampConverter().toJson(json['createdAt'] as DateTime); } return json; } }
方式二:全局转换器提供者(适合统一切换场景)
通过全局状态管理转换器,无需多入口方法,但需注意全局状态的线程安全:
// converter_provider.dart import 'package:json_annotation/json_annotation.dart'; import 'date_time_converter.dart'; class ConverterProvider { static JsonConverter<DateTime, dynamic>? _dateTimeConverter; static void setDateTimeConverter(JsonConverter<DateTime, dynamic> converter) { _dateTimeConverter = converter; } static JsonConverter<DateTime, dynamic> get dateTimeConverter => _dateTimeConverter ?? const DateTimeConverter(); }
模型类使用全局转换器:
@JsonSerializable() class FooBar { final String id; final DateTime createdAt; FooBar({required this.id, required this.createdAt}); factory FooBar.fromJson(Map<String, dynamic> json) { final converter = ConverterProvider.dateTimeConverter; final processedJson = Map<String, dynamic>.from(json); if (processedJson.containsKey('createdAt')) { processedJson['createdAt'] = converter.fromJson(processedJson['createdAt']); } return _$FooBarFromJson(processedJson); } Map<String, dynamic> toJson() { final json = _$FooBarToJson(this); final converter = ConverterProvider.dateTimeConverter; if (json.containsKey('createdAt')) { json['createdAt'] = converter.toJson(json['createdAt'] as DateTime); } return json; } }
在应用初始化时切换转换器:
// Firestore环境(如Flutter App) ConverterProvider.setDateTimeConverter(const TimestampConverter()); // 普通Dart环境(如GCP Cloud Run) ConverterProvider.setDateTimeConverter(const DateTimeConverter());
关键注意事项
- 跨环境兼容性:通过条件导入避免非Firestore环境依赖
cloud_firestore包,保证模型类可在纯Dart环境正常使用。 - 字段一致性:模型类始终持有
DateTime类型字段,所有转换逻辑仅在序列化/反序列化阶段处理,不影响业务代码对DateTime的直接使用。 - 批量字段处理:若模型中有多个
DateTime字段,可封装工具方法批量处理字段转换,避免重复代码。
内容的提问来源于stack exchange,提问作者Jimmy Forrester-Fellowes
相关产品推荐
相关产品推荐

