You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在运行时切换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());

关键注意事项

  1. 跨环境兼容性:通过条件导入避免非Firestore环境依赖cloud_firestore包,保证模型类可在纯Dart环境正常使用。
  2. 字段一致性:模型类始终持有DateTime类型字段,所有转换逻辑仅在序列化/反序列化阶段处理,不影响业务代码对DateTime的直接使用。
  3. 批量字段处理:若模型中有多个DateTime字段,可封装工具方法批量处理字段转换,避免重复代码。

内容的提问来源于stack exchange,提问作者Jimmy Forrester-Fellowes

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 04:42:14