如何在Dart中将包含DateTime字段的对象转换为JSON?

17

我尝试将一个对象转换为JSON格式。

  var obj = { "dt": new DateTime.now() };
  var s = stringify(obj);

运行时抛出异常:"在对象上调用toJson方法失败。"

这是预期的,因为DateTime类没有toJson方法。但在这种情况下我该怎么办呢?

Javascript的JSON.stringify函数有一个可选参数replacer,它允许我提供自己的序列化方式来处理任何对象,即使该对象没有toJson方法。在Dart中是否有类似的功能,或者我可以通过某种方式扩展DateTime类以使用自己的toJson方法?

4个回答

19

JSON 转换仅适用于地图、列表、字符串、数字、布尔或 null。那么如果您的对象包含另一种类型,例如 DateTime

DateTime → JSON

让我们从以下对象开始:

class Person {
  Person(this.name, this.birthdate);
  String name;
  DateTime birthdate;
}
你可以像这样将它转换为一个地图:
final person = Person('Bob', DateTime(2020, 2, 25));

Map<String, dynamic> map = {
 'name': person.name,
 'birthdate': person.birthdate,
};

如果您现在尝试使用jsonEncode(或json.encode)对此进行编码,您将会收到一个错误,因为DateTime不能直接序列化。有两个解决方案。

解决方案1

您可以首先自己序列化它,像这样:

Map<String, dynamic> map = {
  'name': person.name,
  'birthdate': person.birthdate.toIso8601String(),
};

final jsonString = json.encode(map);

Note:

这里是 toStringtoIso8601String 之间的区别:

2020-02-25 14:44:28.534 // toString()
2020-02-25T14:44:28.534 // toIso8601String()

toIso8601String没有任何空格,这使得它更适合于转换和发送到可能无法处理空格的API上。

解决方案2

您可以在jsonEncode上使用可选的toEncodable函数参数。

import 'dart:convert';

void main() {
  final person = Person('Bob', DateTime(2020, 2, 25));

  Map<String, dynamic> map = {
    'name': person.name,
    'birthdate': person.birthdate,
  };

  final toJson = json.encode(map, toEncodable: myDateSerializer);
}

dynamic myDateSerializer(dynamic object) {
  if (object is DateTime) {
    return object.toIso8601String();
  }
  return object;
}

toEncodable 函数只是将输入转换为字符串或可转换为字符串的内容,以便 jsonEncode 可以将其转换为字符串。

JSON → DateTime

这里没有什么特别的。你只需要将字符串解析成需要的类型即可。在 DateTime 的情况下,可以使用它的 parsetryParse 方法。

final myMap= json.decode(jsonString);
final name = myMap['name'];
final birthdateString = myMap['birthdate'];
final birthdate = DateTime.parse(birthdateString);
final decodedPerson = Person(name, birthdate);

请注意,如果字符串的格式无法解析为DateTime对象,则parse会抛出异常。

作为模型类

class Person {

  Person(this.name, this.birthdate);

  String name;
  DateTime birthdate;

  Person.fromJson(Map<String, dynamic> json)
      : name = json['name'],
        birthdate = DateTime.tryParse(json['birthdate']),

  Map<String, dynamic> toJson() {
    return {
      'name': name,
      'birthdate': birthdate.toIso8601String(),
    };
  }
}

如果日期格式错误,这不会引发异常,但birthdate将为null

备注

  • 请参见此处获取我的详细回答。
  • 感谢此答案指导我正确的方向。

4

Zdeslav Vojkovic的回答现在已经过时了。

dart:convert中的JSON.encode()方法有一个可选的toEncodable方法,用于对无法本地序列化为JSON的对象进行调用。然后由用户提供一个闭包来返回DateTime的适当序列化。


2
在我看来,dart:json库中的一个缺陷是stringify不支持回调函数来序列化缺乏toJson实现的类型。奇怪的是,parse函数确实支持reviver参数来自定义反序列化过程。可能的原因是用户可以为他们自己的类型添加toJson,而核心库将处理“本地”类型,但省略了DateTime(也许是因为JSON中的日期序列化并不是一个简单的故事)。
也许团队的目标是使用自定义类型而不是Maps(这很好),但这里的缺点是,如果您的自定义类型包含其他10个属性和1个DateTime类型的属性,则需要实现toJson来序列化它们所有,甚至整数和字符串。希望一旦Mirror API准备就绪,将会有库通过读取类型的反射信息来实现“带外”序列化,下面是一个例子。 Dart的承诺是我可以在服务器和客户端上使用我的类型,但如果我必须手动实现所有模型的序列化/反序列化,那么它太笨拙了,无法使用。
另外,DateTime本身在格式方面也不是非常灵活,因为除了返回格式无用的toString方法之外,没有其他方法。
所以这里有一些选择:
  • DateTime包装(或派生)在您自己的类型中,该类型提供toJson
  • 修补json.stringifyJsonValue并提交更改,或至少提交问题:)
  • 使用一些第三方库,例如json-object(仅是一个例子,据我所知它也不支持DateTime序列化
对于任何一种选择,我都不是很满意:)

1
日期格式化在这里 http://api.dartlang.org/docs/releases/latest/intl/DateFormat.html 在stringify()中添加替换函数参数存在一个错误 https://code.google.com/p/dart/issues/detail?id=5911 - Greg Lowe
谢谢,我不知道这个(我检查了核心库,但'intl'必须单独安装)。我猜ISO8601/RFC3339格式仍然必须“手动”构建,对吧?而且由于它们并不真正“本地化”,我不确定它们是否真的适合intl。需要一个好的序列化库,但我想在镜像API完成之前没有意义。 - Zdeslav Vojkovic
DateTime对象具有可用于提取其数据的字段,而无需尝试解析字符串。或者可以轻松生成硬编码格式的新DateFormat(“yyyy-MM-dd”)以打印ISO表单。它不是本地化的,但它可以打印您想要的内容。还有一个序列化库,如果可用,则可以使用镜像,但也可以让您编写硬编码规则。它更注重能够将数据读回dart,而不是生成供其他程序使用的JSON。 - Alan Knight
当然,我知道这一点 - 这就是我所提到的“手动构建”,但我觉得有点笨拙。没有扩展方法,因此您无法将其附加到DateFormat上。因此,它需要成为我的代码中的单独函数(或硬编码实例),这意味着数据格式化在DateTime、DateFormat和我的函数/实例之间分散。 - Zdeslav Vojkovic
@Zdeslav,像intl这样的包使用pub而不是core导入,因为这意味着VM镜像可以更小,从而使其启动更快。这是Alan提到的序列化库的链接http://api.dartlang.org/docs/releases/latest/serialization.html,请查看SimpleJsonFormat。 - Greg Lowe
我知道,我没有反对意见 - 我只是错过了它。如果我没记错的话,序列化库在这里并不能帮助,因为它返回一个 Map,如果其中包含一个 DateTime 字段,则会遇到相同的问题。由于 DateTime 经常被使用,我认为应该扩展 'json.stringify' 来支持它(例如以 RFC3339 UTC 格式)。 - Zdeslav Vojkovic

0
我已经在Dart pub.dev上添加了一个新的包,它允许对结构内的对象进行JSON序列化。这个Jsonize包可以序列化和反序列化自定义类,并且可以直接处理DateTime:
  List<dynamic> myList = [1, "Hello!", DateTime.now()];
  var jsonRep = Jsonize.toJson(myList);
  var myDeserializedList = Jsonize.fromJson(jsonRep);

那么我们就用你的例子来做吧

  var obj = { "dt": new DateTime.now() };
  var s = Jsonize.toJson(obj);
  var obj2 = Jsonize.fromJson(s);

但也可以做到这一点

  var obj = DateTime.now();
  var s = Jsonize.toJson(obj);
  var dt = Jsonize.fromJson(s);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接