我需要将Flutter应用程序中的Dart对象存储到Firestore中。
这个对象包括一个枚举属性。
最佳解决方案是如何序列化/反序列化这个枚举属性?
作为字符串
作为整数
我找不到任何简单的方法来做到这一点。
Flutter能够生成JSON序列化代码。您可以在这里找到教程。它引用了json_annotation包,还支持枚举类型的序列化。所以,您只需要使用此工具,并使用@JsonValue
注释您的枚举值。
根据代码文档:
用于指定枚举值的序列化方式的注释。
基本上就是这样了。现在让我通过一个简单的示例代码来说明一下。假设有一个车辆枚举:
import 'package:json_annotation/json_annotation.dart';
enum Vehicle {
@JsonValue("bike") BIKE,
@JsonValue("motor-bike") MOTOR_BIKE,
@JsonValue("car") CAR,
@JsonValue("truck") TRUCK,
}
然后您可以在其中一个模型中使用此枚举,例如 vehicle_owner.dart
,它看起来像这样:
import 'package:json_annotation/json_annotation.dart';
part 'vehicle_owner.g.dart';
@JsonSerializable()
class VehicleOwner{
final String name;
final Vehicle vehicle;
VehicleOwner(this.name, this.vehicle);
factory VehicleOwner.fromJson(Map<String, dynamic> json) =>
_$VehicleOwnerFromJson(json);
Map<String, dynamic> toJson() => _$VehicleOwnerToJson(this);
}
根据json生成指南,您需要提供以下内容。现在,您需要运行构建器或watcher,让Flutter生成代码:
flutter pub run build_runner build
那么生成的代码将如下所示。看一下已经根据您的@JsonValue
注解生成的_$VehicleEnumMap
:
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'vehicle_owner.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
// more generated code omitted here ....
const _$VehicleEnumMap = {
Vehicle.BIKE: 'bike',
Vehicle.MOTOR_BIKE: 'motor-bike',
Vehicle.CAR: 'car',
Vehicle.TRUCK: 'truck',
};
简而言之,使用以下最新的枚举序列化实现:
fromJson -> YourEnum.values.byName("property")
toJson -> YourEnum.property.name
toJson/fromJson
只需将这两个函数添加到您的枚举中即可。 请注意,您也可以简单地在类中创建这些函数。
enum Manufacturer {
mercedes,
volkswagen,
toyota,
ford;
String toJson() => name;
static Manufacturer fromJson(String json) => values.byName(json);
}
class Car {
final String name;
final Manufacturer manufacturer;
Car(this.name, this.manufacturer);
Map<String, dynamic> toJson() {
return {
"name": name,
"manufacturer": manufacturer.toJson(), // Alternative: manufacturer.name
};
}
static Car fromJson(Map<String, dynamic> jsonData) => Car(
jsonData['name'],
Manufacturer.fromJson(jsonData['manufacturer']), // Alternative: Manufacturer.values.byName(jsonData['manufacturer'])
);
}
EnumHelper扩展
,但据我所知,在这种通用类中无法访问YouEnum.values
. 您可以通过以下方式实现toJson(). 您可能会对此问题感兴趣,他们正在讨论将此类函数实现到原生Dart语言中。 - PaulGunter的回答是正确的,只是有点不完整。
JSON可序列化确实可以处理将枚举转换为字符串并从字符串转换回来,以下是生成的示例代码:
const _$HoursEnumMap = <Hours, dynamic>{
Hours.FullTime: 'FullTime',
Hours.PartTime: 'PartTime',
Hours.Casual: 'Casual',
Hours.Contract: 'Contract',
Hours.Other: 'Other'
};
然后,它会使用这个相当晦涩的函数将其转换回来:
T _$enumDecode<T>(Map<T, dynamic> enumValues, dynamic source) {
if (source == null) {
throw ArgumentError('A value must be provided. Supported values: '
'${enumValues.values.join(', ')}');
}
return enumValues.entries
.singleWhere((e) => e.value == source,
orElse: () => throw ArgumentError(
'`$source` is not one of the supported values: '
'${enumValues.values.join(', ')}'))
.key;
}
我对此感到非常厌烦,因此决定制作一个小型软件包来简化复杂性,并且对我来说非常方便:
https://pub.dev/packages/enum_to_string
至少它经过了单元测试,而不是复制/粘贴的解决方案。欢迎添加或提出请求。
enum Status { void }
,void
是 Dart 中的保留字,但我们在其他语言系统中有这个值。在 Dart 中,我们将枚举声明为 enum Status { void_ }
,有什么建议如何处理? - LHJ我的做法是只保存枚举的索引。
假设你有一个枚举:
enum Location {
EARTH,
MOON,
MARS,
}
并且有一个包含该枚举的类,其中具有以下方法:
/// Returns a JSON like Map of this User object
Map<String, dynamic> toJSON() {
return {
"name": this.name,
"location": this.location.index,
};
}
/// Returns [Player] build from a map with informationen
factory Player.fromJson(Map<String, dynamic> parsedJson) {
return new Player(
name: parsedJson['name'],
location: Location.values.elementAt(
parsedJson['location'],
),
);
}
更新
在@JamesAllen提到可维护性的回答后,我想出了这个新的解决方案:
extension LocationExtension on Location {
String get name => describeEnum(this);
}
Location parseLocation(final String locationName) {
switch (locationName) {
case 'earth':
return Location.earth;
case 'moon':
return Location.moon;
case 'mars':
return Location.mars;
default:
throw Exception('$locationName is not a valid Location');
}
}
在你的toJson/fromJson中进行以下操作:
/// Returns a JSON like Map of this User object
Map<String, dynamic> toJSON() {
return {
"name": this.name,
"location": this.location.name,
};
}
/// Returns [Player] build from a map with informationen
factory Player.fromJson(Map<String, dynamic> parsedJson) {
return new Player(
name: parsedJson['name'],
location: parseLocation(parsedJson['location']),
);
class Player
{
String name;
Gender gender;
// functions for jsonEncode and jsonDecode!
Player.fromJson(Map<String, dynamic> json)
: name = json['name'],
gender = getGenderEnum(json['gender']);
Map<String, dynamic> toJson() => {
'name': name,
'gender': getGenderText(gender);
};
}
enum Gender
{
MALE,
FEMALE,
DIVERSE,
}
String getGenderText(Gender gen)
{
switch(gen)
case Gender.MALE:
return "male";
case Gender.FEMALE:
return "female";
case Gender.DIVERSE:
return "diverse";
}
Gender getGenderEnum(String gen) {
for (Gender candidate in Gender.values) {
if (gen == getGenderText(candidate))
return candidate;
}
return Gender.MALE;
}
enum Color {
/// The [jsonValue] must not change in time!
red(10), // Can be numbers
blue(20),
green("myGreen"), // Can be strings as well
gray(40),
yellow(50);
final dynamic jsonValue;
const Color(this.jsonValue);
static Color fromValue(jsonValue) =>
Color.values.singleWhere((i) => jsonValue == i.jsonValue);
}
main() {
var myValue = Color.green.jsonValue;
var myEnum = Color.fromValue(myValue);
print(myEnum);
}
import 'package:jsonize/jsonize.dart';
enum Color with JsonizableEnum {
red("rd"),
blue("bl"),
green("grn"),
gray("gry"),
yellow("yl");
@override
final dynamic jsonValue;
const Color(this.jsonValue);
}
void main() {
// Register your enum
Jsonize.registerEnum(Color.values);
Map<String, dynamic> myMap = {
"my_num": 1,
"my_str": "Hello!",
"my_color": Color.green,
};
var jsonRep = Jsonize.toJson(myMap);
var hereIsMyMap = Jsonize.fromJson(jsonRep);
print(hereIsMyMap);
}
import 'package:jsonize/jsonize.dart';
enum Color with JsonizableEnum {
red("rd"),
blue("bl"),
green("grn"),
gray("gry"),
yellow("yl");
@override
final dynamic jsonValue;
const Color(this.jsonValue);
}
class MyClass implements Jsonizable<MyClass> {
String? str;
MyClass([this.str]);
factory MyClass.empty() => MyClass();
// Jsonizable implementation
@override
String get jsonClassCode => "mc";
@override
dynamic toJson() => str;
@override
MyClass? fromJson(value) => MyClass(value);
}
void main() {
// Register enums and classes
Jsonize.registerEnum(Color.values);
Jsonize.registerClass(MyClass.empty());
Map<String, dynamic> myMap = {
"my_num": 1,
"my_str": "Hello!",
"my_color": Color.green,
"my_dt": DateTime.now(),
"my_class": MyClass("here I am!")
};
var jsonRep = Jsonize.toJson(myMap);
var hereIsMyMap = Jsonize.fromJson(jsonRep);
print(hereIsMyMap);
}
最好的方法是使用枚举整数值,因为它最容易从/转换为int/enum类型。
当你修改枚举时,你需要注意只在结尾添加新的枚举值,否则持久化的值将会变得无效。
https://pub.dartlang.org/packages/built_value提供了类的代码生成,并具有自己的枚举类型,并为您执行JSON(de)序列化。
https://pub.dartlang.org/packages/json_serializable似乎直接支持Dart枚举,但我自己没有使用过。
在启动我的第一个AWS Amplify项目时,我在他们的库中发现了这个有趣的方法:
// only to be used internally by amplify-flutter library
T? enumFromString<T>(String? key, List<T> values) =>
values.firstWhereOrNull((v) => key == enumToString(v));
它的调用方式如下:
Post.fromJson(Map<String, dynamic> json)
: id = json['id'],
type = enumFromString<PostCategory>(json['type'], PostCategory.values),
... // Other props
我必须承认,这种方法比我使用开关读取字符串和输出枚举的自定义方法要聪明得多。现在我知道我不会很快被大型科技公司雇用了...
// JSON deserialization
PostStatusE getPostStatusEnumByString(String type) {
switch (type) {
case "draft":
return PostStatusE.draft;
break;
...
我最喜欢的方法是使用built_value包,它具有EnumClass
,允许使用注释控制序列化值。这似乎是最安全和最强大的解决方案,因为您完全将序列化值与枚举名称或其索引分离,使您能够重命名和重新排序枚举值而不会破坏序列化。对我来说,这比其他答案都更胜一筹。
缺点是需要进行一些设置。将以下内容添加到pubspec.yaml中(用最新版本替换版本):
dependencies:
built_collection: ^5.1.1
built_value: ^8.1.2
...
dev_dependencies:
build_runner: ^2.1.4
built_value_generator: ^8.1.2
然后像这样编写你的枚举 - 使用wireNumber
注解来告诉它你想要每个枚举序列化为的整数值。或者,如果你想要序列化为字符串,请将其替换为wireName
,例如@BuiltValueEnumConst(wireName: 'foo')
part 'my_enum.g.dart';
class MyEnum extends EnumClass {
// Use wireNumber to serialise to an int, or wireName to serialise to a String
@BuiltValueEnumConst(wireNumber: 0)
static const MyEnum foo = _$foo;
@BuiltValueEnumConst(wireNumber: 1)
static const MyEnum bar = _$bar;
const MyEnum._(String name) : super(name);
static BuiltSet<MyEnum> get values => _$values;
static MyEnum valueOf(String name) => _$valueOf(name);
static Serializer<MyEnum> get serializer => _$myEnumSerializer;
}
enum_serializers.dart
):library serializers;
import 'package:built_value/serializer.dart';
import 'package:built_value/standard_json_plugin.dart';
part 'enum_serializers.g.dart';
// add all of the built value types that require serialization
@SerializersFor([
MyEnum,
// add any more enums you need serializing here
])
// Also add StandardJsonPlugin. Without this, it will by default output value lists instead of a JSON-compatible value map
final Serializers enumSerialisers = (_$enumSerializers.toBuilder()..addPlugin(StandardJsonPlugin())).build();
然后生成相应的.g.dart
文件:
flutter packages pub run build_runner build --delete-conflicting-outputs
现在你可以像这样进行序列化和反序列化:
final enumValue = MyEnum.foo;
// serialize:
int serializedValue = enumSerialisers.serializeWith(MyEnum.serializer, enumValue) as int;
// deserialize:
EnumValue deserializedValue = enumSerialisers.deserializeWith(MyEnum.serializer, serializedValue) as EnumValue;
Vnum
https://github.com/AmirKamali/Flutter_Vnum - Amir.n3t