Enum from String

2020-02-05 12:30发布

问题:

I have an Enum and a function to create it from a String because i couldn't find a built in way to do it

enum Visibility{VISIBLE,COLLAPSED,HIDDEN}

Visibility visibilityFromString(String value){
  return Visibility.values.firstWhere((e)=>
      e.toString().split('.')[1].toUpperCase()==value.toUpperCase());
}

//used as
Visibility x = visibilityFromString('COLLAPSED');

but it seems like i have to rewrite this function for every Enum i have, is there a way to write the same function where it takes the Enum type as parameter? i tried to but i figured out that i can't cast to Enum.

//is something with the following signiture actually possible?
     dynamic enumFromString(Type enumType,String value){

     }

回答1:

Using mirrors you could force some behaviour. I had two ideas in mind. Unfortunately Dart does not support typed functions:

import 'dart:mirrors';

enum Visibility {VISIBLE, COLLAPSED, HIDDEN}

class EnumFromString<T> {
  T get(String value) {
    return (reflectType(T) as ClassMirror).getField(#values).reflectee.firstWhere((e)=>e.toString().split('.')[1].toUpperCase()==value.toUpperCase());
  }
}

dynamic enumFromString(String value, t) {
  return (reflectType(t) as ClassMirror).getField(#values).reflectee.firstWhere((e)=>e.toString().split('.')[1].toUpperCase()==value.toUpperCase());
}

void main() {
  var converter = new EnumFromString<Visibility>();

  Visibility x = converter.get('COLLAPSED');
  print(x);

  Visibility y = enumFromString('HIDDEN', Visibility);
  print(y);
}

Outputs:

Visibility.COLLAPSED
Visibility.HIDDEN


回答2:

Mirrors aren't always available, but fortunately you don't need them. This is reasonably compact and should do what you want.

enum Fruit { apple, banana }

// Convert to string
String str = Fruit.banana.toString();

// Convert to enum
Fruit f = Fruit.values.firstWhere((e) => e.toString() == str);

assert(f == Fruit.banana);  // it worked

Fix: As mentioned by @frostymarvelous in the comments section, this is correct implementation:

Fruit f = Fruit.values.firstWhere((e) => e.toString() == 'Fruit.' + str);


回答3:

Collin Jackson's solution didn't work for me because Dart stringifies enums into EnumName.value rather than just value (for instance, Fruit.apple), and I was trying to convert the string value like apple rather than converting Fruit.apple from the get-go.

With that in mind, this is my solution for the enum from string problem

enum Fruit {apple, banana}

Fruit getFruitFromString(String fruit) {
  fruit = 'Fruit.$fruit';
  return Fruit.values.firstWhere((f)=> f.toString() == fruit, orElse: () => null);
}


回答4:

My solution is identical to Rob C's solution but without string interpolation:

T getEnumFromString<T>(Iterable<T> values, String value) {
  return values.firstWhere((type) => type.toString().split(".").last == value,
      orElse: () => null);
}


回答5:

This is all so complicated I made a simple library that gets the job done:

https://pub.dev/packages/enum_to_string

import 'package:enum_to_string:enum_to_string.dart';

enum TestEnum { testValue1 };

convert(){
    String result = EnumToString.parse(TestEnum.testValue1);
    //result = 'testValue1'

    String resultCamelCase = EnumToString.parseCamelCase(TestEnum.testValue1);
    //result = 'Test Value 1'

    final result = EnumToString.fromString(TestEnum.values, "testValue1");
    // TestEnum.testValue1
}


回答6:

I improved Collin Jackson's answer using Dart 2.7 Extension Methods to make it more elegant.

enum Fruit { apple, banana }

extension EnumParser on String {
  Fruit toFruit() {
    return Fruit.values.firstWhere(
        (e) => e.toString().toLowerCase() == 'fruit.$this'.toLowerCase(),
        orElse: () => null); //return null if not found
  }
}

main() {
  Fruit apple = 'apple'.toFruit();
  assert(apple == Fruit.apple); //true
}


回答7:

There are a couple of enums packages which allowed me to get just the enum string rather than the type.value string (Apple, not Fruit.Apple).

https://pub.dartlang.org/packages/built_value (this is more up to date)

https://pub.dartlang.org/packages/enums

void main() {
  print(MyEnum.nr1.index);            // prints 0
  print(MyEnum.nr1.toString());       // prints nr1
  print(MyEnum.valueOf("nr1").index); // prints 0
  print(MyEnum.values[1].toString())  // prints nr2
  print(MyEnum.values.last.index)     // prints 2
  print(MyEnum.values.last.myValue);  // prints 15
}  


回答8:

@Collin Jackson has a very good answer IMO. I had used a for-in loop to achieve a similar result prior to finding this question. I am definitely switching to using the firstWhere method.

Expanding on his answer this is what I did to deal with removing the type from the value strings:

enum Fruit { apple, banana }

class EnumUtil {
    static T fromStringEnum<T>(Iterable<T> values, String stringType) {
        return values.firstWhere(
                (f)=> "${f.toString().substring(f.toString().indexOf('.')+1)}".toString()
                    == stringType, orElse: () => null);
    }
}

main() {
    Fruit result = EnumUtil.fromStringEnum(Fruit.values, "apple");
    assert(result == Fruit.apple);
}

Maybe someone will find this useful...



回答9:

I had the same problem with building objects from JSON. In JSON values are strings, but I wanted enum to validate if the value is correct. I wrote this helper which works with any enum, not a specified one:

class _EnumHelper {


 var cache = {};

  dynamic str2enum(e, s) {
    var o = {};
    if (!cache.containsKey(e)){
      for (dynamic i in e) {
        o[i.toString().split(".").last] = i;
      }
      cache[e] = o;
    } else {
      o = cache[e];
    }
    return o[s];
  }
}

_EnumHelper enumHelper = _EnumHelper();

Usage:

enumHelper.str2enum(Category.values, json['category']);

PS. I did not use types on purpose here. enum is not type in Dart and treating it as one makes things complicated. Class is used solely for caching purposes.



回答10:

Here is the function that converts given string to enum type:

EnumType enumTypeFromString(String typeString) => EnumType.values
    .firstWhere((type) => type.toString() == "EnumType." + typeString);

And here is how you convert given enum type to string:

String enumTypeToString(EnumType type) => type.toString().split(".")[1];


回答11:

I had the same problem in one of my projects and existing solutions were not very clean and it didn't support advanced features like json serialization/deserialization.

Flutter natively doesn't currently support enum with values, however, I managed to develop a helper package Vnum using class and reflectors implementation to overcome this issue.

Address to the repository:

https://github.com/AmirKamali/Flutter_Vnum

To answer your problem using Vnum, you could implement your code as below:

@VnumDefinition
class Visibility extends Vnum<String> {
  static const VISIBLE = const Visibility.define("VISIBLE");
  static const COLLAPSED = const Visibility.define("COLLAPSED");
  static const HIDDEN = const Visibility.define("HIDDEN");

  const Visibility.define(String fromValue) : super.define(fromValue);
  factory Visibility(String value) => Vnum.fromValue(value,Visibility);
}

You can use it like :

var visibility = Visibility('COLLAPSED');
print(visibility.value);

There's more documentation in the github repo, hope it helps you out.



回答12:

I think my approach is slightly different, but might be more convenient in some cases. Finally, we have parse and tryParse for enum types:

import 'dart:mirrors';

class Enum {
  static T parse<T>(String value) {
    final T result = (reflectType(T) as ClassMirror).getField(#values)
        .reflectee.firstWhere((v)=>v.toString().split('.').last.toLowerCase() == value.toLowerCase()) as T;
    return result;
  }

  static T tryParse<T>(String value, { T defaultValue }) {
    T result = defaultValue;
    try {
      result = parse<T>(value);
    } catch(e){
      print(e);
    }
    return result;
  }
}

EDIT: this approach is NOT working in the Flutter applications, by default mirrors are blocked in the Flutter because it causes the generated packages to be very large.



回答13:

You can add an extension, it will still be accessed from the enum!

usage example: this answer



标签: dart