enum_assist 0.0.1 copy "enum_assist: ^0.0.1" to clipboard
enum_assist: ^0.0.1 copied to clipboard

outdated

Seamlessly generate extension methods and json conversion classes for your enums using enum_assist

Enum Assist

Seamlessly generate extension methods and json conversion classes for your enums!

Motivation #

Dart enums can be a bit tedious to work with. Serializing them to/from json, using switch statements based off their values, or using describeEnum or split('.') to get the value's name are a couple of examples where working with enums could be improved.

Writing extensions has been a great way to add extra functionality to classes & enums. Though, you'll find yourself writing the same extensions over and over again. I was getting tired of copying and pasting code and changing a couple of things whenever I created a new enum. So I did what any other sane developer would do, I took a couple of weeks to create an automation tool to save me time. 🤪

So welcome enum_assist into your lifeproject! The fastest way to writing extension methods and json conversion classes for your enums!

Check out the example or the index to see what it can do.

Index #

How to use #

Install #

To use enum_assist, you will need to set up the build_runner (code-generator) for your project. First, add the following packages to your pubspec.yaml:

depenedencies:
    enum_assist_annotation:

dev_dependencies:
    build_runner:
    enum_assist:

What are these packages?

  • enum_assist_annotation, contains all the annotations that enum_assist references to generate code.
    • Why a Dependency: The annotations are a part of your code, so enum_assist_annotation must be part of the dependencies
  • enum_assist, the code generator for all the annotations
    • Why a Dev Dependency?: The generator only generates the code. So its technically not part of your code which means that enum_assist can be part of the dev_dependencies
  • build_runner, a tool that any code-generator package uses to generate code

Generating the Code #

Build Runner Commands #

  • If your package depends on Flutter:

    flutter pub run build_runner build
    
  • If your package does not depend on Flutter:

    dart pub run build_runner build
    

If you're new to build_runner, I suggest taking a look at these commands & options

File Setup #

Each file will need to start with the enum_assist import and the part keyword.

import 'package:enum_assist_annotation/enum_assist_annotation.dart';

part 'example.ge.dart';

Here's a full example of example.dart:

import 'package:enum_assist_annotation/enum_assist_annotation.dart';

part 'example.ge.dart';

@EnumAssist()
enum SuperHero {
  superman,
  spiderman,
  ironman,
}

Features #

Default Extension Methods #

The following methods will be generated with every enum annotated with EnumAssist

Name #

Returns the name of the enum value.

var greet = Greeting.friendly;

greet.name; // friendly

Description #

Returns the EnumValue.description of the enum value in a human readable format.

var greet = Greeting.friendly;

greet.description; // A friendly greeting

To Int #

Returns the EnumValue.intValue of the enum value.

Greeting.professional.toInt; // 0
Greeting.friendly.toInt; // 1
Greeting.relaxed.toInt; //2

Readable #

Returns the EnumValue.readable of the enum value in a human readable format.

var greet = Greeting.friendly;

greet.readable; // Friendly

Serialized #

Returns the EnumValue.serializedValue of the enum value.

Specific case formatting can be done with serializedFormat (either EnumAssist or build.yaml)

var greet = Greeting.friendly;

greet.serialized; // friendly

map/maybeMap #

Each enum will generate a .map(...) & .maybeMap(...) method, which is equivalent to pattern matching.

The base of all custom extension methods.

This enum will be used as reference:

@EnumAssist()
enum Greeting {
  professional,
  friendly,
  relaxed,
}

Map #

.map() provides callbacks for each enum value. All callbacks are required args and can return any value.

var greet = Greeting.friendly;

final whatDoYouSay = greet.map(
    professional: 'Hello Sir',
    friendly: 'Hello',
    relaxed: 'Saaa dude!',
);

whatDoYouSay; // Hello

Maybe Map #

.maybeMap() provides callbacks for each enum value, plus an orElse callback.
orElse is the only required arg.

var greet = Greeting.friendly;

final whatDoYouSay = greet.maybeMap(
    orElse: '*blank stare*',
    professional: 'Hello Sir',
);

whatDoYouSay; // *blank stare*

Return Type #

.map<T>() and .maybeMap<T>() use generics to provide the return type of the callback.

While its not necessary to define the return type, it is recommended to do so.
Defining the return type protects you by forcing a specific return type.

  • Defined Return Type: String

    var greet = Greeting.friendly;
    
    final whatDoYouSay = greet.map<String>(
        professional: 'Hello Sir',
        friendly: 'Hello',
        // relaxed: 123, // compile error: `123` is not a type String
        relaxed: 'Saaa dude!',
    );
    
    whatDoYouSay.runtimeType; // String
    

Here are a couple of examples of inferred return types:

  • Return type: String

    var greet = Greeting.friendly;
    
    final whatDoYouSay = greet.map(
        professional: 'Hello Sir',
        friendly: 'Hello',
        relaxed: 'Saaa dude!',
    );
    
    whatDoYouSay.runtimeType; // String
    
  • Return type: String?

    var greet = Greeting.friendly;
    
    final whatDoYouSay = greet.map(
        professional: 'Hello Sir',
        friendly: null,
        relaxed: 'Saaa dude!',
    );
    
    whatDoYouSay.runtimeType; // String?
    
  • Return type: Object?

    var greet = Greeting.friendly;
    
    final whatDoYouSay = greet.map(
        professional: 'Hello Sir',
        friendly: null,
        relaxed: 3,
    );
    
    whatDoYouSay.runtimeType; // Object?
    

    Note: Just because you can, doesn't mean you should...
    With great power, comes great responsibility

Custom Extensions #

enum_assist allows you to create your own extension methods for your enum, with minimal code!

There are two ways to create your own extension methods.
You start by creating a class that extends MapExtension or MaybeExtension.

IMPORTANT: All properties (other than value) need to be defined within the super constructor!


Map Extension #

There are 4 arguments:

  • value (required), The value for the enum field.
    • Can be any const value
    • Type is inferred from the class's type arguments
  • methodName (required), The name of the method to be created
    • This must be unique and should not be use by any other extension classes
  • docComment (optional), The doc comment for the method
    • multi-line strings are supported.
    • /// will be prepended to each new line.
  • allowNulls (optional), Whether or not to return null values
    • default: false
    • For the return type to be nullable, this must be set to true

An example:

class SaidByExt extends MapExtension<String> {
  const SaidByExt(String value)
    : super(
        value,
        methodName: 'saidBy',
        docComment: 'Greeting that is said by...',
        allowNulls: false, // default
      );
}

Add it to the the extensions property

@EnumAssist()
enum Greeting {
  @EnumValue(extensions: [SaidByExt('Executives')])
  professional,
  @EnumValue(extensions: [SaidByExt('Co-workers')])
  friendly,
  @EnumValue(extensions: [SaidByExt('Friends')])
  relaxed,
}

Generated code:

/// Greeting that is said by...
String get saidBy {
  return map<String>(
    professional: 'Executives',
    friendly: 'Co-workers',
    relaxed: 'Friends',
  );
}

Maybe Extension #

There are 5 arguments, 4 are the same as MapExtension

The 5th argument is:

  • defaultValue (required), used as the orElse callback value.
    • Can be any const value
    • Type is inferred from the class's type argument

Expected return values:

  • defaultValue:
    • If the extension value is not defined
    • If the extension value is null AND allowNulls is false
  • null
    • If the extension value is null AND allowNulls is true
  • Defined value
    • If the extension value is defined
class HowFrequentExt extends MaybeExtension<int?> {
  const HowFrequentExt(int? value)
    : super(
        value,
        methodName: 'howFrequent',
        docComment: '1-10, How often this greeting is used.\n\n`null` if never used',
        defaultValue: 0,
        allowNulls: true,
      );
}

Add it to the the extensions property

@EnumAssist()
enum Greeting {
  @EnumValue(extensions: [])
  professional,
  @EnumValue(extensions: [HowFrequentExt(3)])
  friendly,
  @EnumValue(extensions: [HowFrequentExt(8)])
  relaxed,
  @EnumValue(extensions: [HowFrequentExt(null)])
  rude,
}

Generated Code:

/// 1-10, How often this greeting is used
///
/// `null` if never used
int? get howFrequent {
  return maybeMap<int?>(
    // returns default value
    //? if theres an argument provided, it does nothing.
    orElse: HowFrequentExt(3).defaultValue,
    professional: HowFrequentExt(3).defaultValue,
    friendly: 3,
    relaxed: 8,
    rude: null,
  );
}

Notice This:
For the generated code to access the defaultValue, it must create an instance of the extension class. If there is a required argument, the arg must be passed to avoid exceptions. Therefore, the required arg will be provided & ignored to return default value.

Note: If an extension is omitted (like professional), the default value will be used.
null will only be returned if declared with a null value. (like rude)

Json Converter #

Serializing enums almost always requires a switch statement.
Mistakes can easily be made when converting from a String (or other types) to an enum.

The Json converter class is a great way to handle your enums' serialization.

The name of json converter class will be ${NAME_OF_ENUM}Conv

For a detailed example, go to toJson/fromJson

Here's a quick example:

// Generated Json Converter class
final conv = GreetingConv();

final greet = Greeting.professional;
conv.toJson(greet); // professional

final greetJson = 'professional';
conv.fromJson(greetJson); // Greeting.professional

Examples #

Map Example #

Lets create a .response method for the enum Greeting enum.
This method will return a String with the response to the greeting.

We first need to create our extension class.

class ResponseExt extends MapExtension<String> {
  const ResponseExt(String value)
    : super(
        value,
        methodName: 'response',
        docComment: 'The response to a greeting',
      );
}

Note:
The MapExtension class also has an allowNulls argument, which defaults to false.
This can be set to true to change the return type nullable.

Next, we need to add our extension to the enum.
This can be done by annotating the enum's values with EnumValue, And then adding the extension to the extensions field.

Note:
Because the .map(...) requires all args to be defined, we must add the ResponseExt extension to ALL enum fields.
Failure to do so will result in an error when generating the code.

@EnumAssist()
enum Greeting {
  @EnumValue(
    extensions: [
      ResponseExt('Hello, how do you do?'),
    ],
  )
  professional,

  @EnumValue(
    extensions: [
      ResponseExt('Hey! Hows it going?'),
    ],
  )
  friendly,

  @EnumValue(
    extensions: [
      ResponseExt('Whats up my dude!?'),
    ],
  )
  relaxed,
}

After the build_runner has run, you can now access the .response method on the Greeting enum.

var greet = Greeting.friendly;

greet.response; // Hey! Hows it going?

Greeting.relaxed.response; // Whats up my dude!?

MaybeMap Example #

Lets create a .isCool method for the Greeting enum.
This method will return true only if the enum value is friendly or relaxed. Or else it will return false.

We first need to create our extension class.

class IsCoolExt extends MaybeExtension<bool> {
  const IsCoolExt(bool value)
    : super(
        value,
        methodName: 'isCool',
        defaultValue: false,
        docComment: 'Is this a cool greeting?',
      );
}

Note:
The MaybeExtension class also has an allowNulls argument, which defaults to false.
This can be set to true if you want the return type to be nullable.

Note:
The constructor could take a named argument with a default value to reduce the amount of code needed.

const IsCoolExt([bool value = true])

Next, we need to add our extension to the enum.
This can be done by adding the EnumValue annotation to any enum field. And then adding the extension to the extensions list.

@EnumAssist()
enum Greeting {
  professional,

  @EnumValue(
    extensions: [
      IsCoolExt(true),
    ],
  )
  friendly,

  @EnumValue(
    extensions: [
      IsCoolExt(true),
    ],
  )
  relaxed,
}

Notice This:
We did not annotate professional with EnumValue or IsCoolExt.
This is because .maybeMap(...) doesn't require all callbacks to be defined.

The generator will use the defaultValue from IsCoolExt as the return value.

After the build_runner has run, you can now access the .isCool method on the Greeting enum.

var greet = Greeting.friendly;
greet.isCool; // true

Greeting.professional.isCool; // false

toJson & fromJson #

Serializing enums almost always requires a switch statement.
Mistakes can easily be made when converting from a string to an enum.

Json Converter Classes are a great way to handle this.

Let's create an enum for the two examples below, Using json_serializable & Manually Serializing.

@EnumAssist()
enum SuperHeroes {
  @EnumValue(serializedValue: 'Capitan America')
  capitanAmerica,

  @EnumValue(serializedValue: 'Black Widow')
  blackWidow,

  @EnumValue(serializedValue: 'Dr. Strange')
  drStrange,
}

Using json_serializable #

json_serializable will automatically serialize enums to json by using describeEnum. This is great if your enum's values are exactly the same as the json values. But that is not always the case, just like our SuperHeroes enum.

Let's use our generated class SuperHeroesConv fix that problem!

Here is our example class, annotated with JsonSerializable

@JsonSerializable()
class Character {
  const Character(
    this.secretIdentity,
    this.hero,
    this.powerLevel,
  );

  final String secretIdentity;
  final SuperHeroes hero;

  factory Character.fromJson(Map<String, dynamic> json) =>
      _$CharacterFromJson(json);
}

By default, json_serializable will serialize our hero field using the literal enum value as a String.

final steve = Character('Steve Rogers', SuperHeroes.capitanAmerica);

final json = steve.toJson();

print(json['hero']); //capitanAmerica

To tell json_serializable to convert the hero field with the values provided by EnumValue.serializedValue, you'll need to annotated the field in your class

  final String secretIdentity;

  @SuperHeroesConv()
  final SuperHeroes hero;

Note: If hero were nullable, you would need to annotate the field with a nullable converter

  final String secretIdentity;

  @SuperHeroesConv.nullable
  final SuperHeroes hero;

After you run the build_runner, the json value for the hero field will now be

final steve = Character('Steve Rogers', SuperHeroes.capitanAmerica);

final json = steve.toJson();

print(json['hero']); // Capitan America

Manually Serializing #

Here is an example of what your class could look like

class Character {
  const Character(
    this.secretIdentity,
    this.hero,
  );

  final String secretIdentity;
  final SuperHeroes hero;

  Map<String, dynamic> toJson() {
    return {
      'secretIdentity': secretIdentity,
      'hero': superHeroToJson(hero),
    };
  }

  factory Character.fromJson(Map<String, dynamic> json) {
    return Character(
      json['secretIdentity'] as String,
      superHeroFromJson(json['hero'] as String),
    );
  }
}

String superHeroToJson(SuperHeroes hero) {
  switch (hero) {
    case SuperHeroes.capitanAmerica:
      return 'Capitan America';
    case SuperHeroes.blackWidow:
      return 'Black Widow';
    case SuperHeroes.drStrange:
      return 'Dr. Strange';
  }
}

SuperHeroes superHeroFromJson(String hero) {
  switch (hero) {
    case 'Capitan America':
      return SuperHeroes.capitanAmerica;
    case 'Black Widow':
      return SuperHeroes.blackWidow;
    case 'Dr. Strange':
      return SuperHeroes.drStrange;
    default:
      throw Exception('Could not find superhero for $hero');
  }
}

It's a lot of work to just convert an enum to json!
Thankfully, the generated class SuperHeroConv can do all of the work here

Our toJson and fromJson methods will now look like this

class Character {
  const Character(
    this.secretIdentity,
    this.hero,
  );

  final String secretIdentity;
  final SuperHeroes hero;

  static const _conv = SuperHeroesConv();

  Map<String, dynamic> toJson() {
    return {
      'secretIdentity': secretIdentity,
      'hero': _conv.toJson(hero),

      // you could also use the `serialized` method here
      // which is the same as _conv.toJson(hero)
      //
      // 'hero': hero.serialized,
    };
  }

  factory Character.fromJson(Map<String, dynamic> json) {
    return Character(
      json['secretIdentity'] as String,
      _conv.fromJson(json['hero'] as String),
    );
  }
}

And that's it!

And here is what the hero's value would look like

final steve = Character('Steve Rogers', SuperHeroes.capitanAmerica);

final json = steve.toJson();

print(json['hero']); // Capitan America

Build Configuration #

If you want to customize the settings for each enum, you can specify it inside your build.yaml file.

For example:

targets:
  $default:
    builders:
      enum_assist:
        enabled: true
        options:
          # possible values:
          # - true
          # - false
          # default: true
          create_json_conv: true

          # possible values:
          # - camel
          # - capital
          # - constant
          # - dot
          # - header
          # - kebab
          # - no
          # - none
          # - pascal
          # - path
          # - sentence
          # - snake
          # - swap
          # default: none
          serialized_format: none

          # possible values:
          # - true
          # - false
          # default: true
          use_doc_comment_as_description: true

          # possible values:
          # - true
          # - false
          # default: false
          use_int_value_for_serialization: false

Annotations #

Enum Assist #

Used to specify the enum you want to generate code for

Create Json Conversion #

field: createJsonConv

Whether or not to create a json converter class (non-nullable & nullable) for your enum.

enum_assist_annotation depends on json_annotation to generate JsonConverter classes.
Even if you do not use json_serializable or json_annotation in your project, you can still use the generated conversion classes in your code.

Go to Json Converter Classes for an example

Serialized Format #

field: serializedFormat

Used By:

Sets the format you want the values of the enum to be serialized to.

enum_assist depends on change_case to format the serialized value.
The possible values for the build.yaml file is any value from the SerializedFormat enum

Here's an example:

  • SerializedFormat.none (default)

    static const _professionalName = 'professional';
    
  • SerializedFormat.snake

    static const _veryProfessionalName = 'very_professional';
    

Use Doc Comment As Description #

Whether or not to use the enum value's doc comment as the description of the enum value.

If set to false, the description will return null, unless defined via EnumValue.description.

Enum:

@EnumAssist(
  useDocCommentAsDescription: true,
)
enum Greeting {
  /// A professional greeting
  professional,
  /// A friendly greeting
  friendly,
  /// A relaxed greeting
  ///
  /// Which is my favorite!
  relaxed,
}
final greet = Greeting.friendly;

greet.description; // A friendly greeting

// "A relaxed greeting
//
// Which is my favorite!"
Greeting.relaxed.description;

Use Int Value For Serialization #

Whether or not to use the enum's int value for serialization.

Setting this to true, the serializedValue field will be ignored.

@EnumAssist(
  useIntValueForSerialization: true,
)
enum Greeting {
  professional,
  friendly,
  relaxed,
}
final greet = Greeting.friendly;

greet.serialized; // 1

Greeting.relaxed.serialized; // 2

Notice This: Instead of returning friendly, it will return the intValue of the friendly field, which is 1

Enum Value #

Used to customize the generator for a specific enum value.

Readable #

Provides the name for readable of the enum value. The name should be a human readable format.

@EnumAssist()
enum Greeting {
  @EnumValue(readable: 'Formal')
  professional,
  friendly,
  relaxed,
}
final greet = Greeting.friendly;

greet.readable; // Friendly

Greeting.professional.readable; // Formal

Description #

Provides the description for the description of the enum value.

The description should be a human readable format.
For Example: for Example.isReallyCool, the description could be The example is really cool!

Expected Return Value:

@EnumAssist()
enum Greeting {
  /// A professional greeting
  @EnumValue(description: 'Recommended to use in the work place')
  professional,
  /// A friendly greeting
  friendly,
  relaxed,
}
final greet = Greeting.friendly;

greet.description; // A friendly greeting

Greeting.professional.description; // Recommended to use in the work place

Int Value #

Provides the int value for toInt of the enum value.

0 indexed integers used to represent the enum value.
When a value is assigned to EnumValue.intValue, the value will be passed on to the next enum field, incrementing by 1.

For example:

@EnumAssist()
enum ResponseCodes {
  @EnumValue(intValue: 200)
  ok,
  created,
  accepted,

  @EnumValue(intValue: 400)
  badRequest,
  unauthorized,
  @EnumValue(intValue: 403)
  forbidden,
  notFound,

  @EnumValue(intValue: 500)
  internalServerError,
  notImplemented,
  badGateway,
  serviceUnavailable,
}

Their return values will be:

ResponseCode.ok.toInt; // 200
ResponseCode.created.toInt; // 201
ResponseCode.accepted.toInt; // 202
ResponseCode.badRequest.toInt; // 400
ResponseCode.unauthorized.toInt; // 401
ResponseCode.forbidden.toInt; // 403
ResponseCode.notFound.toInt; // 404
ResponseCode.internalServerError.toInt; // 500
ResponseCode.notImplemented.toInt; // 501
ResponseCode.badGateway.toInt; // 502
ResponseCode.serviceUnavailable.toInt; // 503

Serialized Value #

Provides the serialized representation of the enum value for serialized and json converter classes.

Specific case formatting can be done with serializedFormat (either EnumAssist or build.yaml)

Note: While the type for serializedValue is Object?, the only accepted types are String, and int.

@EnumAssist()
enum Greeting {
  @EnumValue(serializedValue: 'formal')
  professional,
  friendly,
  @EnumValue(serializedValue: 3) // serializedValue accepts type Object?
  relaxed,
}
final greet = Greeting.friendly;

greet.serialized; // friendly

Greeting.professional.serialized; // formal

Gretting.relaxed.serialized; // 3

Use Doc Comment As Description #

Whether or not to use the enum value's doc comment as the description of the enum value.

If set to false, the description will return null, unless defined via EnumValue.description.

@EnumAssist()
enum Greeting {
  /// A professional greeting
  @EnumValue(useDocCommentAsDescription: false)
  professional,
  /// A friendly greeting
  friendly,
  relaxed,
}
final greet = Greeting.friendly;

greet.description; // A friendly greeting

Greeting.professional.description; // null

Extensions #

Custom Extension Methods to be created for the enum, specified with the value of the enum field.

Extension classes must extend

Go to Examples for an example of how to create custom extensions.

7
likes
0
pub points
0%
popularity

Publisher

verified publishermrgnhnt.com

Seamlessly generate extension methods and json conversion classes for your enums using enum_assist

Repository (GitHub)
View/report issues

License

unknown (LICENSE)

Dependencies

analyzer, build, build_config, change_case, enum_assist_annotation, json_annotation, meta, source_gen, source_helper

More

Packages that depend on enum_assist