Reputation: 941
I'd like to make use of Dart's new (experimental) enum
feature instead of using stacks of static const Strings, but what's the best way to serialize/deserialize enum
variables using JSON? I've made it work this way, but surely there's a better solution:
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
If you serialize using the index, you can get locked into keeping your enums in the same order forever, so I'd much prefer to use some kind of String form. Anyone figured this out?
Upvotes: 26
Views: 22187
Reputation: 1562
This is answered before but not with generics. Make your class implement below:
abstract class JsonDecodable<T> {
factory JsonDecodable.fromJson(Map<String, dynamic> json) =>
throw UnimplementedError();
}
Example (with a bonus int list conversion):
class TestClass implements JsonDecodable<TestClass> {
TestEnum? enumProperty;
List<int>? intListProperty;
TestClass(this.enumProperty, this.intListProperty);
factory TestClass.fromJson(dynamic json) {
return TestClass(
TestEnum.values.byName(json['enumProperty'])
(json['intListProperty'] as List).map((e) => e as int).toList());
}
}
Usage:
import 'dart:convert';
jsonDecode(yourJsonString);
Upvotes: 0
Reputation: 403
Take a look to my answer here
In your case you can replace the enum Color
with your enum Status
:
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);
}
Or if you want to use the jsonize package you can do this:
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);
}
Upvotes: 0
Reputation: 2419
If you're using Dart 2.15.0+ (Flutter 2.8.0+)
You can make use of the new name
property added to enums.
To convert it to json value, you would do
Status status1 = Status.stopped;
String jsonValue = status1.name;
print(jsonValue); // prints "stopped"
To convert it back to enum, you would do
String jsonValue = "stopped";
Status deserializedStatus = Status.values.byName(jsonValue);
print(deserializedStatus); // prints "Status.stopped"
Upvotes: 9
Reputation: 161
You can try in model class.
...
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;
}
...
Upvotes: 0
Reputation: 2872
Use name
property and byName
method of enums
Here is a sample code to show how to use it:
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;
}
Upvotes: 14
Reputation: 1248
i suggest to use google aswome library called json_serializable (this link)
as said here : Annotate enum values with JsonValue to specify the encoded value to map to target enum entries. Values can be of type String or int.
enum StatusCode {
@JsonValue(200)
success,
@JsonValue('500')
weird,
}
Upvotes: 5
Reputation: 1073
As one of the answer previously suggested, if you share the same implementation on the client and server, then serializing the name is I think the best way and respects the open/closed principle in the S.O.L.I.D design, stating that:
"software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification"
Using the index instead of the name would mess up all the logic of your code if you ever need to add another member to the Enum. However using the name would allow extension.
Bottom line, serialize the name of your enum and in order to deserialize it properly, write a little function that given an Enum as a String, iterates over all the members of the Enum and returns the appropriate one. Such as:
Status getStatusFromString(String statusAsString) {
for (Status element in Status.values) {
if (element.toString() == statusAsString) {
return element;
}
}
return null;
}
So, to serialize:
Status status1 = Status.stopped;
String json = JSON.encode(status1.toString());
print(json) // prints {"Status.stopped"}
And to deserialize:
String statusAsString = JSON.decode(json);
Status deserializedStatus = getStatusFromString(statusAsString);
print(deserializedStatus) // prints Status.stopped
This is the best way I've found so far. Hope this helps !
Upvotes: 19