Dungeons & Dragons guide, dice and wand

Overcoming Dart’s Enum Limitations

Ra9r
4 min readMay 29, 2019

In the DnD world there are a number of things that when expressed in code are traditionally expressed as enumerations. A good example of this is the concept of “size”, which can be one of the following values …

  • Tiny
  • Small
  • Medium
  • Large
  • Huge
  • Gargantuan

In Dart code, the above could be easily expressed as :

enum DnDSize { Tiny, Small, Medium, Large, Huge, Gargantuan };

Unfortunately, Dart enum is a very limiting concept when it is used to reflect values that are stored in a database or retrieved as a JSON or XML data protocol. The main reason for the limitation is the because it lacks the ability to assign specific values each of the enumerated types.

Take our example above, notice there is no way to specify that DnDSize.Tiny is equal to the String 'T' or alternatively the integer value 0 .

A better example would be the concept of “difficulty” in DnD. Consider the following table of challenge levels …

  • Very Easy (Difficulty Rating: 5)
  • Easy (Difficulty Rating: 10)
  • Medium (Difficulty Rating: 15)
  • Hard (Difficulty Rating: 20)
  • Very Hard (Difficulty Rating: 25)
  • Nearly Impossible (Difficulty Rating: 30)

If we tried to express the above as an enumerated type it might look like the following in Dart …

enum DnDChallenge { 
VeryEasy,
Easy,
Medium,
Hard,
VeryHard,
NearlyImpossible,
};

However if we expressed it using enum then we would loose any ability to express the Difficulty Rating values for each. So how do we address this short coming? Well ... we express it as a class that is specially constructed to provide all the value of an enumerated type, but still retain all the capabilities of a class.

To best understand we will look at the two examples above. First let us create DnDSize using a class instead of an enum .

class DnDSize {
final String _name;
const DnDSize._(this._name); @override
String toString() {
return _name;
}
static const DnDSize Tiny = DnDSize._('Tiny');
static const DnDSize Small = DnDSize._('Small');
static const DnDSize Medium = DnDSize._('Medium');
static const DnDSize Large = DnDSize._('Large');
static const DnDSize Huge = DnDSize._('Huge');
static const DnDSize Gargantuan = DnDSize._('Gargantuan');
}

In this example we have created a class DnDSize that has a private property _name that can only be set by the constructor which is also private. The only instances of this class that are therefore possible are those created in lines 10 - 15 using the static const modifiers.

The benefit of this implementation over the enum equivalent is the addition of a custom toString() method that we can used to convert the instances of DnDSize into a string.

NOTE: This is particular useful when you need to convert DnDSize.Tiny into a string inside a JSON message for example.

But we don’t have to stop there … because this is a class, we can also add other methods. In this example it would be very helpful to be able to parse a string into an instance of DnDSize .

/// Attempts to parse the string representation of
/// a "size" into its corresponding DnDSize instance
/// or return null if unable to parse.
static DnDSize tryParse(String val) {
switch (val.toLowerCase()) {
case 'tiny':
return Tiny;
case 'small':
return Small;
case 'medium':
return Medium;
case 'large':
return Large;
case 'huge':
return Huge;
case 'gargantuan':
return Gargantuan;
default:
print('$val is not a valid size');
return null;
}
}

This approach works just as well for DnDChallenge as we can see in this next example ...

class DnDChallenge {
final int _value;

const DnDChallenge(this._value);
int toInt() {
return _value;
}
@override
String toString() {
if (_value <= VeryEasy._value) {
return 'Very Easy ($_value)';
} else if (_value <= Easy._value) {
return 'Easy ($_value)';
} else if (_value <= Medium._value) {
return 'Medium ($_value)';
} else if (_value <= Hard._value) {
return 'Hard ($_value)';
} else if (_value <= VeryHard._value) {
return 'Very Hard ($_value)';
} else {
return 'Nearly Impossible ($_value)';
}
}
static const DnDChallenge VeryEasy = DnDChallenge(5);
static const DnDChallenge Easy = DnDChallenge(10);
static const DnDChallenge Medium = DnDChallenge(15);
static const DnDChallenge Hard = DnDChallenge(20);
static const DnDChallenge VeryHard = DnDChallenge(25);
static const DnDChallenge NearlyImpossible = DnDChallenge(30);
}

In this example we are able to create an internal integer _value for the difficulty rating as also provide a custom toString() method.

NOTE You may notice in this example that the constructor is not private. This enables the creation of instances of DnDChallenge that aren't one of the specific enumerated types. This is optional and is presented here simply to show that it is possible.

Conclusion

Just because Dart’s implementation of enum is limited doesn't mean you can't still have rich enumerated types. Using the pattern above you can still have code that looks like this ...

switch (size) {
case DnDSize.Tiny:
// ...
case DnDSize.Small:
// ...
default:
// ...
}

But also be able to do things like DnDSize.toString() and DnDChallenge.toInt() .

In effect ... you can have your enumerated cake and eat it too!

--

--

Ra9r

Startup-Founder • Code-Monkey • Travel-Enthusiast • Aspiring-Health-Nut • Cancer Warrior • FI/RE'd