Generic Enumerations For Dart
Introduction
Enumerations are ideal when we want to model choosing from a limited set of constant values.
In Dart, the value of an enum
instance resolves to a String
.
The package generic_enum
can be used together with
generic_enum_builder
to build extensions
supporting:
- mapping of enum instances to a value of arbitrary data-type,
- json-serialization.
Usage
To use this library include generic_enum
as
dependencies in your pubspec.yaml
file.
Include generic_enum_builder
,
and build_runner
as dev_dependencies.
The example below shows how to define the enumeration DpiResolution
and map each enum instance to a value of type double
.
Click to show source code.
import 'package:generic_enum/generic_enum.dart';
// 0. Import package exception_templates.
import 'package:exception_templates/exception_templates.dart';
// 1. Add a part statement pointing to the generated file.
part 'dpi_resolution.g.dart';
// 2. Define an enumeration
// and annotate it with @GenerateJsonExtension().
@GenerateValueExtension(
valueType: double,
values: const {'90.0', '300.0', '600.0'},
)
@GenerateJsonExtension()
enum DpiResolution { low , medium, high }
The required steps are detailed below:
-
Add the import directives shown above.
-
Add a part statement referencing the generated file
dpi_resolution.g.dart
. -
Define an enumeration and annotate it with:
- @GenerateValueExtension() to generated the enum getters
value
,valueMap
andstringValue
- @GenerateJsonExtension() to generate the enum method
toJson()
andTo<EnumName>.fromJson(json)
.
- @GenerateValueExtension() to generated the enum getters
-
Configure the build targets (and amend the generate_for entry). In your local
build.yaml
file add configurations for the builders provided by the package generic_enum_builder.Click to show file content.
targets: $default: builders: # Configure the builder `pkg_name|builder_name` generic_enum_builder|extension_builder: # Only run this builder on the specified input. enabled: true generate_for: - lib/*.dart
Note: The file
dpi_resolution.dart
should be an asset that can be resolved by the builder. To limit the number of files scanned for annotationed classes during the build process one can use agenerate_for
statement in the builder configuration. -
Build the project by navigating to the project root directory and running the command:
$ dart run build_runner build --delete-conflicting-outputs
-
For the example presented here, the build process will generate the file
dpi_resolution.g.dart
.Click to show file content.
// GENERATED CODE - DO NOT MODIFY BY HAND part of 'dpi_resolution.dart'; // ************************************************************************** // ValueGenerator // ************************************************************************** /// Extension on `DpiResolution` providing value-getters. extension DpiResolutionValue on DpiResolution { /// Returns a map of type `Map<double, DpiResolution>` mapping /// each declared enum value to an instance of `DpiResolution`. double get value => const <DpiResolution, double>{ DpiResolution.low: 90.0, DpiResolution.medium: 300.0, DpiResolution.high: 600.0, }[this]!; /// Returns the String identifier of an instance of `DpiResolution`. String get stringValue => const <DpiResolution, String>{ DpiResolution.low: 'low', DpiResolution.medium: 'medium', DpiResolution.high: 'high', }[this]!; /// Returns a mapping of instance name to enum instance. Map<String, DpiResolution> get valueMap => const <String, DpiResolution>{ 'low': DpiResolution.low, 'medium': DpiResolution.medium, 'high': DpiResolution.high, }; } // ************************************************************************** // JsonGenerator // ************************************************************************** /// Extension providing the functions `fromJson()`, `toJson()`, /// and the getter `jsonEncoded`. extension ToDpiResolution on DpiResolution { /// Converts [json] to an instance of `DpiResolution`. static DpiResolution fromJson(Map<String, dynamic> json) { final index = (json['index']) as int?; if (index == null) { throw ErrorOf<DpiResolution>( message: 'Error deserializing json to DpiResolution.', invalidState: 'json[index] returned null.', expectedState: 'A map entry: {index: int value}.'); } if (index >= 0 && index < DpiResolution.values.length) { return DpiResolution.values[index]; } else { throw ErrorOf<DpiResolution>( message: 'Function fromJson could not find ' 'an instance of type DpiResolution.', invalidState: 'DpiResolution.values[$index] out of bounds.'); } } /// Converts `this` to a map `Map<String, dynamic>`. Map<String, dynamic> toJson() => {'index': DpiResolution.values.indexOf(this)}; /// Converts `this` to a json encoded `String`. String get jsonEncoded => '{"index":${DpiResolution.values.indexOf(this)}}'; }
Enum - Value Mapping
The annotation @GenerateValueExtension
requires the following parameters:
Type valueType
: The type of the constants mapped to the enum instances.Set<String> values
. The entries are copied verbatim by the generator and must represent valid const instances of the data-typevalueType
. The number of entries must match the number of enum instances.
Limitations
Because of this issue it is not possible to pass an instance of enum
to the function jsonEncode(Object object)
(provided by dart:convert
)
even if the function toJson()
is defined in an extension on the enum
.
Alternative ways to serialize an instance of enum are:
- Use the generated getter
toJsonEncoded
to retrieve a json encodedString
. - Pass the result of
toJson()
tojsonEncode
. - Use a full blown serialization approach like
json_serializable
. This is recommended if your project already usesjson_serializable
.
When it comes to deserialization, the usual approach is to define a factory constructor named fromJson
.
This is not possible since extensions do not support constructors. Moreover, static extension-methods
are accessed by specifying the extension name.
To keep the notation similar to the "usual approach", the extension containing the static method fromJson
is named To + Enum Name, see example below.
import 'dart:convert';
import 'package:test/test.dart';
import 'dpi_resolution.dart';
// The enum instance.
final low = DpiResolution.low;
// Encoding to Map<String, dynamic>.
// Returns: {'index': 0}
Map<String, dynamic> json = low.toJson();
// Encoding to String.
String jsonEncoded0 = jsonEncode(low); // Throws error! Extensions not available for dynamic types.
String jsonEncoded1 = jsonEncode(json) // Using dart:convert.
String jsonEncoded2 = low.jsonEncoded; // Using the generated getter.
expect(jsonEncoded1, jsonEncoded2);
// Decoding (notice the prefix "To").
expect(ToDpiResolution.fromJson(json), low);
expect(ToDpiResolution.fromJson(jsonDecode(jsonEncoded1)), low);
Examples
Further examples on how to define and build generic enumeration classes can be found in the package generic_enum_example.
Features and bugs
Please file feature requests and bugs at the issue tracker.
Libraries
- generic_enum
- Library providing the classes: