21

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?

montyr75
  • 881
  • 1
  • 5
  • 8
  • I think you need the enums name as well (like a type name). How would you know which type to deserialize to if you don't have the enum name. You should also be aware of that it is not a good idea to serialize/deserialize enums for purposes like persistence. If You add a value somewhere in front of existing values the indexes for given names may change and break serialization/deserialization because the Dart enum doesn't allow to assign custom enum values. – Günter Zöchbauer Dec 02 '14 at 07:59
  • Yes, exactly, Gunter. As I mentioned with my question, using indexes in this manner seems fragile, but I can't see another way. My hope was to be able use enums in communications between a client and server app, but unless someone else comes up with more insight, I'll just have to stick with Strings for now. – montyr75 Dec 02 '14 at 15:36
  • 1
    If you use it only for client/server and share the implementation I think it's fine using enum. I find the workaround (class with static values) more flexible and also not too verbose to create and prefer it over enum except for the simplest cases. – Günter Zöchbauer Dec 02 '14 at 15:39
  • Enums are designed to allow for minification, so if you want to use their names as strings in the browser, this basically boils down to the same problem as using mirrors with minification. I'd say the best approach is to use/build a serialization package which on the server and in dartium at development time can use mirrors (or even just parsing the toString output), but at build time will automatically generate serialization code for the client. – Greg Lowe Dec 03 '14 at 08:05
  • 2021! now you can use extensions! https://medium.com/flutter/enums-with-extensions-dart-460c42ea51f7 – Akbar Pulatov Feb 23 '21 at 14:39

6 Answers6

16

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 !

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
Pierre Roudaut
  • 1,013
  • 1
  • 18
  • 32
  • 3
    Yes, unfortunately, this is the best way I know, too. Its inelegance and possible performance ramifications have thus far kept me from using enums much at all, and never on data that needs to be serialized (most data, for me, as it happens). The enum implementation is one of the very few things I dislike about Dart. – montyr75 Aug 13 '15 at 04:38
12

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;
}
Anoop Thiruonam
  • 2,567
  • 2
  • 28
  • 48
  • 1
    Earliest posted version of best current answer using .name property. Why another later repeat of this same answer is getting upvotes... don't know. You should be getting the credit here. Thank you! – Bill Patterson Mar 22 '23 at 19:54
7

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"
Michal Šrůtek
  • 1,647
  • 16
  • 17
2

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,
  }
FxRi4
  • 1,096
  • 10
  • 15
0

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;
}

...

Çağlar YILMAZ
  • 111
  • 2
  • 4
0

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);
}
cabbi
  • 393
  • 2
  • 12