使用JSON序列化/反序列化Dart的新枚举类型的最佳方法是什么?

21

我希望利用Dart的新功能(实验性的enum),而不是使用大量静态常量字符串,但如何在使用JSON时序列化/反序列化enum变量?我已经让它以这种方式工作,但肯定有更好的解决方案:

enum Status {
  none,
  running,
  stopped,
  paused
}

Status status1 = Status.stopped;
Status status2 = Status.none;

String json = JSON.encode(status1.index);
print(json);   // prints 2

int index = JSON.decode(json);
status2 = Status.values[index];
print(status2);  // prints Status.stopped
如果您使用索引进行序列化,那么您就可能被锁定在将枚举保持同样顺序的情况下,因此我更喜欢使用某种字符串形式。有人解决了这个问题吗?

如果你使用枚举的索引进行序列化,就会导致你必须一直按照同样的顺序排列枚举值,所以我更倾向于使用某种字符串形式。有人解决过这个问题吗?


我认为你需要枚举名称(就像类型名称)一样。如果没有枚举名称,你怎么知道要反序列化成哪种类型。你还应该知道,将枚举用于持久化等目的并不是一个好主意。如果在现有值之前添加一个值,则给定名称的索引可能会更改,并且会破坏序列化/反序列化,因为Dart枚举不允许分配自定义枚举值。 - Günter Zöchbauer
是的,没错,Gunter。正如我在问题中提到的那样,以这种方式使用索引似乎很脆弱,但我看不到其他方法。我的希望是能够在客户端和服务器应用程序之间使用枚举类型进行通信,但除非有人想出更多的见解,否则我现在只能坚持使用字符串了。 - montyr75
1
如果您只用于客户端/服务器并共享实现,我认为使用枚举是可以的。我发现解决方法(具有静态值的类)更灵活,而且创建起来也不会太啰嗦,除了最简单的情况外,我更喜欢它而不是枚举。 - Günter Zöchbauer
枚举类型的设计目的是允许进行代码压缩,因此如果你想在浏览器中将它们的名称作为字符串使用,基本上就归结为与使用代码压缩时的镜像问题相同。我认为最好的方法是使用/构建一个序列化包,在开发时服务器和dartium可以使用镜像(甚至只需解析toString输出),但在构建时会自动生成用于客户端的序列化代码。 - Greg Lowe
2021!现在您可以使用扩展!https://medium.com/flutter/enums-with-extensions-dart-460c42ea51f7 - Akbar Pulatov
6个回答

16

正如之前一位回答所建议的那样,如果客户端和服务器上实现相同,则我认为将名称序列化是最好的方法,并且尊重S.O.L.I.D设计中的开闭原则,即:

"软件实体(类、模块、函数等)应该对扩展开放,但对修改关闭"

如果使用索引而不是名称,则如果您需要向枚举添加另一个成员,则会破坏代码的所有逻辑。然而,使用名称将允许扩展。

总之,将枚举的名称序列化,为了正确反序列化它,编写一个小函数,给定一个作为字符串的枚举,迭代枚举的所有成员并返回相应的成员。例如:

Status getStatusFromString(String statusAsString) {
  for (Status element in Status.values) {
     if (element.toString() == statusAsString) {
        return element;
     }
  }
  return null;
}

因此,要进行序列化:

Status status1 = Status.stopped;
String json = JSON.encode(status1.toString());
print(json) // prints {"Status.stopped"}

然后进行反序列化:

String statusAsString = JSON.decode(json);
Status deserializedStatus = getStatusFromString(statusAsString);
print(deserializedStatus) // prints Status.stopped

这是到目前为止我发现的最好的方法。希望这能帮到你!


3
是的,不幸的是,这是我所知道的最好的方法。它的不优雅和可能带来的性能影响一直使我不太使用枚举,而且从来不会在需要序列化的数据上使用枚举(对我来说大部分数据都需要序列化)。枚举实现是我对Dart非常不喜欢的极少数事情之一。 - montyr75

12

使用枚举的name属性和byName方法。

以下是一个示例代码,展示如何使用它:

import 'dart:convert';

void main() {
  Person raj = Person(name: 'Raj', favIcecream: Icecream.pista);
  print(raj.toJson());

  Person rajV2 = Person.fromJson(raj.toJson());
  print(rajV2.toJson());

  final isBothInstanceEqual = raj == rajV2;
  print('> Both instancecs are equal is $isBothInstanceEqual');
}

enum Icecream {
  vanilla,
  pista,
  strawberry,
}

class Person {
  String name;
  Icecream favIcecream;
  Person({
    required this.name,
    required this.favIcecream,
  });

  Map<String, dynamic> toMap() {
    return {
      'name': name,
      'favIcecream': favIcecream.name, // <- this is how you should save
    };
  }

  factory Person.fromMap(Map<String, dynamic> map) {
    return Person(
      name: map['name'] ?? '',
      favIcecream: Icecream.values.byName(map['favIcecream']), // <- back to enum
    );
  }

  String toJson() => json.encode(toMap());

  factory Person.fromJson(String source) => Person.fromMap(json.decode(source));

  @override
  bool operator ==(Object other) {
    if (identical(this, other)) return true;

    return other is Person &&
        other.name == name &&
        other.favIcecream == favIcecream;
  }

  @override
  int get hashCode => name.hashCode ^ favIcecream.hashCode;
}

1
最早发布的最佳答案使用了.name属性。为什么后来又有相同答案的重复获得了赞...不知道。你应该在这里得到认可。谢谢! - Bill Patterson

8

如果您正在使用Dart 2.15.0+ (Flutter 2.8.0+)

您可以利用新增加的name属性来定义枚举。

将其转换为json值,您可以执行以下操作:

Status status1 = Status.stopped;
String jsonValue = status1.name;
print(jsonValue); // prints "stopped"

要将其转换回枚举,您需要执行以下操作

String jsonValue = "stopped";
Status deserializedStatus = Status.values.byName(jsonValue);
print(deserializedStatus); // prints "Status.stopped"

3

我建议使用谷歌的一个很棒的库,叫做json_serializable(这个链接

正如在这里所说的: 使用@JsonValue注释枚举值,以指定编码值映射到目标枚举条目。 值可以是String或int类型。

enum StatusCode {
   @JsonValue(200)
   success,
   @JsonValue('500')
   weird,
  }

0

您可以在模型类中尝试。

...
YourModel.fromJson(Map<String,dynamic> json){
    status = Status.values.elementAt(json['status']);
}

Map<String, dynamic> toJson(Status status){
   final Map<String, dynamic> data = <String, dynamic>{};
   data['status'] = status.index;
   return data;
}

...


0

请看我的回答这里

在你的情况下,你可以用你的enum Status替换enum Color

enum Status {
  none("nn"), // You can also use numbers as you wish
  running("rn"),
  stopped("st"),
  paused("pa");

  final dynamic jsonValue;
  const Status(this.jsonValue);
  static Status fromValue(jsonValue) =>
      Status.values.singleWhere((i) => jsonValue == i.jsonValue);
}

或者如果你想使用jsonize包,可以这样做:

import 'package:jsonize/jsonize.dart';

enum Status with JsonizableEnum {
  none("nn"),
  running("rn"),
  stopped("st"),
  paused("pa");

  @override
  final dynamic jsonValue;
  const Status(this.jsonValue);
}

void main() {
  // Register your enum
  Jsonize.registerEnum(Status.values);

  Map<String, dynamic> myMap = {
    "my_num": 1,
    "my_str": "Hello!",
    "my_status": Status.running,
  };
  var jsonRep = Jsonize.toJson(myMap);
  var backToLife = Jsonize.fromJson(jsonRep);
  print(backToLife);
}

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