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.

Intro

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.

Model

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.

The Movie has a list of reviews which are either RemoteReview, CriticReview or UserReview.

Standard configuration

Before encoding the movie to JSON we need Circe dependencies. I use Typelevel Scala 2.12.1 (but it is not required).

To encode the movie into JSON you only need some imports as Circe is boilerplate-free.

Below you can see the result of encoding.

There are three things that I do not like:

• the type of sealed trait is a JSON key
• value class UserId is encoded as JSON object with a value,
• Genre is a JSON object with type as a key and empty object as a value.

Let’s see how to customize it.

Custom configuration

Module circe-generic-extras provides a way to set the discriminator which is used for encoding hierarchies. Now the type will be encoded as a field of name type without nesting.

To simplify encoding of value classes we need following generic codecs built using Shapeless (source).

For sealed trait hierarchies, which consist only of case objects, first, we need to define what enum is.

And then it can be used to make the type a JSON value. For this, I used example posted by Travis Brown here.

Having all encoders and decoders defined, now we can build the JSON and make sure that after decoding the Movie still looks the same.

As you can see, this JSON looks much more friendly.

Integration

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.

Having that, whenever you need implicit Encoder[Movie] just put import json.codecs._ there.

Resources

You can see full example here. To learn Shapeless you can read The Type Astronaut’s Guide to Shapeless Book.