Enumeratum 1.4: ValueEnums + Circe
It’s been a while since the last major release of Enumeratum, and in 1.4.0, minor changes include Play 2.5 support, integration library version bumps, and small internal refactorings. More excitingly though, the latest version adds support for a new kind of enumeration, ValueEnum
, as well as an integration with the Circe JSON library.
Points of interest:
- Unlike other value enum implementations, Enumeration’s value enums perform uniqueness checks at compile time to make sure you have unique values across your enum members.
- Circe integration allows you to send and receive JSON data between your front end and your server using the same code
The 1.4.0 release page on Github has a more detailed list of changes, but we’ll specifically go through:
ValueEnums
What is a ValueEnum
? It’s an enum that represents a primitive value (e.g. Int
, Long
, Short
) instead of a String
. I may have just made up the term, but it doesn’t matter as long as you know what I mean.
1 2 3 4 5 6 7 8 |
|
This may sound mundane, since you can already build something like this yourself with the standard library’s Enumeration
(or previous versions of Enumeratum ), but sometimes the most straightforward solutions are suboptimal.
The trouble with Enumeration
The standard lib’s Enumeration
comes with the notion of a customisable id: Int
on each member, which is a great starting point for implementing numbers-based enumerations.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
This funny behaviour is caused by the fact that Enumeration#Value
s (First
, Second
, Third
, Fourth
) are not checked for unique ids at compile time, and are instantiated when their outer Enumeration
object is lazily instantiated. When a Value
is instantiated, its id
is stuffed into a HashMap[Int, Value]
after an assertion check that the id does not already exist in the map.
What has happend in the above example is the enumeration code compiles, but when we call Things.First
, object Things
gets instantiated, and throws an assertion error when val Fourth
is being instantiated with an id of 3, which has already been assigned to Third
and thus is already in the aforementioned HashMap
. This prevents the singleton Things
from getting instantiated, and the next time you try to use it, Scala will throw a NoClassDefFoundError
.
One way to work around this is to write tests for every such Enumeration
to make sure that no one working in the code base has fat-fingered any ids. I’m a big proponent of writing tests, but tests are also code and come with a maintenance and cognitive cost, so I would prefer not having to write tests to make sure my simple value enums can be safely initialised.
This kind of problem is not limited to Enumeration
: careless implementation of something similar may result in arguably freakier outcomes such as silent failures (2 members with the same value but only one of the members can be retrieved by value).
ValueEnum
In version 1.4.0 of Enumeratum, we’ve introduced 3 pairs of traits: IntEnum
and IntEnumEntry
, LongEnum
and LongEnumEntry
, and ShortEnum
and ShortEnumEntry
. As their names suggest, these are value enum traits that allow you to create enums that are backed by Int
, Long
and Short
respectively. Each pair extends ValueEnum
and ValueEnumEntry
. Note that this class hierarchy is a bit extensive for now, and it may be more streamlined in the future.
This is an example of how you would create an Long
based value enum with Play integration (JSON readers and writers, Query string binders, Path binders, Form formatters, etc):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
The findValues
method of ValueEnum
s works similarly to the findValues
method of Enumeratum’s older Enum
, except the macro will ensure that there is a literal value
member or constructor for each enum entry and fails the compilation if more than one member shares the same value.
As the above example demonstrates, there are Play (and standalone Play-JSON) integrations available for this new kind of enum, as well as for UPickle, and Circe.
~~Note that this new feature is not yet available in Scala 2.10 and in the REPL due to Macro expansion differences~~ (update: now works in the REPL and is available for 2.10.x!).
Circe integration
Enumeratum 1.4.0 also adds support for serialising/deserialising to JSON using Circe, an up-and-coming performant and feature-filled JSON library published for both JVM and ScalaJS.
This is how you would use Circe with Enumeratum’s Enum
(integrations for ValueEnum
also exist)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
Conclusion
Hopefully, Enumeratum’s new ValueEnum
implementations will make development easier and safer for engineers out there who need to use value enumerations. Since uniqueness is checked at compile-time, you can save yourself the trouble of writing a bunch of pedantic tests. Circe is a promising JSON library that was really easy to integrate with and I look forward to taking advantage of the fact that it works on both server side and on the front end.
As always, if you have any problems, questions, suggestions, or better yet, PRs, please do not hesitate to get in touch on Github.