【Dart】Enumと文字列を変換する方法

Dart
Dart
この記事は約6分で読めます。

Enumは、定数を表現できますが、インスタンス化、サブクラス化、Mix-in、実装はできません。
そのため、Enumに対して、処理を追加する場合、工夫が必要です。
今回は、3つの方法を使って、処理を追加したいと思います。

  • 拡張関数
  • ユーティリティクラス
  • ヘルパークラス

拡張関数を使った変換

拡張関数(extension yyy on xxx)を使って関数をEnumに処理を追加することができます。
メリットは、Enum名をそのまま使えるため、わかりやすいこと。
デメリットは、Enumごとに処理を追加しないといけないこと、staticが使えないこと。

enum Number { One, Two,}
extension on Number {
  String get name => this.toString().split(".").last;
}
extension on String {
  Number toEnumNumber() {
    return Number.values.firstWhere((e) => e.name == this, orElse: () => null);
  }
}
void main() {
  print(Number.One.name); // One
  print('Two'.toEnumNumber()); // Number.Two
}

Number.One.nameは、Enumから文字列をします。わかりやすいですね。
staticが作れないので、Stringクラスに追加する形になります。
Enumが複数ない場合や、複雑な処理をしないのであれば、いい方法だと思います。

ユーティリティクラスを使った変換

ユーティリティクラスを使う方法は、Enumとの相互変換を行うクラスを作成して、それを呼び出す方法です。
staticで定義して、どこでも呼べるようにします。
メリットは、static定義されているので、Enumであれば、どこでも呼べることです。
デメリットは、Enumとの関連性が薄くなります。

enum Number { One, Two,}
class EnumUtils {
  static String name(value) {
    var sp = value?.toString()?.split('.');
    if (sp == null || sp.length != 2) {
      return null;
    }
    return sp[1];
  }

  static T valueOf<T>(List<T> values, String value) {
    if (value == null || values == null) {
      return null;
    }
    return values.firstWhere((e) => EnumUtils.name(e) == value,
        orElse: () => null);
  }
}
void main() {
  print(EnumUtils.name(Number.One)); // One
  print(EnumUtils.valueOf(Number.values, 'Two')); // Number.Two
}

Enumのユーティリティクラスなので、使う側にとっては、覚えやすいものになっています。
ただ、enumでの型制限ができないため、実行時までエラーに気づかない可能性があります。
今回は、コードで記載しましたが、EnumとStringの変換だけの「enum_to_string」ライブラリも存在します。

enum_to_string | Dart package
Better conversion of ENUMs to string. Dart has annoying EnumName.ValueName syntax when calling enum.toString, this packa...

ヘルパークラスを使った変換

ヘルパークラスを使う方法は、Enumと対になるHelperクラスを作成して、それに処理を任せるという方法です。型の制限が強くなります。

enum Number { One, Two,}
abstract class EnumHelper<T> {
  List<T> values();

  T valueOf(String value) {
    if (value == null) {
      return null;
    }
    return values().firstWhere((e) => name(e) == value, orElse: () => null);
  }

  String name(T value) {
    return value?.toString()?.split('.')?.last;
  }
}
class NumberHelper extends EnumHelper<Number> {
  @override
  List<Number> values() => Number.values;
}
void main() {
  var helper = NumberHelper();
  print(helper.name(Number.One)); // One
  print(helper.valueOf('Two')); // Number.Two
}

型の制限がつけられるところがいいところでしょう。
インスタンス化する必要があるので、メモリの問題がありますが、キャッシュなどで対応することも可能です。

おまけ

拡張関数を使った別の方法もあります。

やり方は、簡単で、enumにアクセス用の項目(from)を1つ追加します。
あとは、拡張関数によって、enumで処理したい処理を追加します。

enum Number { from, One, Two,}
extension NumberIndex on Number {
  String get name => this.toString().split(".").last;
  operator[](String key) =>
    Number.values.firstWhere((e) => e.name == key);
}
void main() {
  print(Number.from["One"]); // Number.One
}

デメリットとして、余計な項目(from)が1つ増えることです。
また、拡張関数で説明したデメリットがあります。

まとめ

大規模や中規模、大人数での開発であれば、コンパイル時のエラーチェックができるヘルパークラスを使うといいでしょう。
小規模であれば、使い勝手がいいユーティリティクラスでもいいと思います。
個人やEnumをそんなに使わない場合は、コピーしやすく、コードが見やすい拡張関数でもいいと思います。

それぞれ、一長一短があるが、Enumが拡張しにくいので、致し方なしですね。
拡張関数のstaticやGenericsのenum対応など、Dartの仕様追加してもらえたらなぁと思うこの頃です。

コメント