While modeling the domain I often use sealed trait hierarchies, value classes, and case classes. They are essential for idiomatic Scala code. However, the encoding and decoding them to JSON can be problematic. Even though Circe provides boilerplate-free generic derivation, the JSON encoded using the default configuration may be confusing. In this post, I describe how to make it more friendly.
Circe is a JSON library for Scala, is a fork of Argonaut and depends on Cats. It provides generic codec derivation using Shapeless with support for algebraic data types (ADT, sealed trait hierarchies).
In this post, I build a simple model with sealed traits, serialize it to JSON using Circe, and show how to customize encoding using Shapeless. You can see the full example here.
First, let’s build the ADT. In this example I use a Movie case class which consists of Name, Genre, Reviews and ReleaseDate. The Genre is a sealed trait with case object values only.
The Review is a hierarchy of case classes. It can be a review written by a user/critic or posted as a link. User and critic are identified using value classes CriticId and UserId.
caseclassCriticId(value: String) extendsAnyVal
caseclassUserId(value: String) extendsAnyVal
url: Option[String], score: Int
cricitId: CriticId, review: String, score: Int
userId: UserId, review: String, score: Int
The Movie has a list of reviews which are either RemoteReview, CriticReview or UserReview.
To integrate custom configuration with auto generic derivation you can create an object that extends AutoDerivation, and then put all the codecs there. In addition, I prefer to use semi-automatic derivation and have the encoders and decoders defined in code.