montyr75
montyr75

Reputation: 941

What is the best way to serialize/deserialize Dart's new enum with JSON?

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

Answers (7)

Baz Guvenkaya
Baz Guvenkaya

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

cabbi
cabbi

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

Michal Šrůtek
Michal Šrůtek

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

&#199;ağlar YILMAZ
&#199;ağlar YILMAZ

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

Anoop Thiruonam
Anoop Thiruonam

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

FxRi4
FxRi4

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

Pierre Roudaut
Pierre Roudaut

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

Related Questions