Chris Johnston
Chris Johnston

Reputation: 361

Enumeratum Circe Serialisation

I have a simple case class like so:

case class ColumnMetadata(name: String,
                          displayName: String,
                          description: Option[String],
                          attributeType: AttributeType)

sealed trait AttributeType extends EnumEntry

case object AttributeType extends Enum[AttributeType] with CirceEnum[AttributeType] {

 val values: immutable.IndexedSeq[AttributeType] = findValues

  case object Number extends AttributeType
  case object Text extends AttributeType
  case object Percentage extends AttributeType
}

And semi-auto encoder:

package object circe {

  implicit val config: Configuration = Configuration.default.withSnakeCaseMemberNames


  implicit val columnMetaDecoder: Decoder[ColumnMetadata] = deriveConfiguredDecoder
  implicit val columnMetaEncoder: Encoder[ColumnMetadata] = deriveConfiguredEncoder

  implicit val columnTypeDecoder: Decoder[AttributeType] = deriveConfiguredDecoder
  implicit val columnTypeEncoder: Encoder[AttributeType] = deriveConfiguredEncoder
}

However when I do a serialisation test:

  ColumnMetadata("column1", "Column 1", Some("column1"), 
    AttributeType.withName("Text")).asJson

I get:

  {
    "name" : "column1",
    "display_name" : "Column 1",
    "description" : "column1",
    "attribute_type" : {
      "Text": {}
    }
  }

When I want:

  {
    "name" : "column1",
    "display_name" : "Column 1",
    "description" : "column1",
    "attribute_type" : "Text"
  } 

It works when I use the automatic derivation but I want to use a semi-auto derivation so I can use feature such as withSnakeCaseMemberNames.

Upvotes: 1

Views: 2524

Answers (1)

Mateusz Kubuszok
Mateusz Kubuszok

Reputation: 27595

This is your error:


  implicit val columnTypeDecoder: Decoder[AttributeType] = deriveConfiguredDecoder
  implicit val columnTypeEncoder: Encoder[AttributeType] = deriveConfiguredEncoder

This will derive new codecs, treating AttributeType as any other sealed trait, so it will use discrimination value (semi auto always ignores existing codecs of the type that they are deriving!).

So you are deriving a new AttributeType codecs, put them in scope where they are used, and so making these new implementations have higher priority than the ones from an companion object. Closer implicit always wins.

If you won't derive the codecs (because there are already existing implementations provided by CirceEnum trait) then it will work as you expect.

Additionally instead of doing this:

  implicit val columnMetaDecoder: Decoder[ColumnMetadata] = deriveConfiguredDecoder
  implicit val columnMetaEncoder: Encoder[ColumnMetadata] = deriveConfiguredEncoder

you can just do this:

// make sure that Configuration is in scope by e.g. importing it
// or putting it in package object in the same package as case class
@ConfiguredJsonCodec
case class ColumnMetadata(name: String,
                          displayName: String,
                          description: Option[String],
                          attributeType: AttributeType)

This will spare you effort of creating package of codecs and importing them manually, everywhere you need them. E.g.

// imports

package object models_package {

  private[models_package] implicit val config: Configuration = Configuration.default.withSnakeCaseMemberNames
}
package models_package

// imports

@ConfiguredJsonCodec
case class ColumnMetadata(name: String,
                          displayName: String,
                          description: Option[String],
                          attributeType: AttributeType)

sealed trait AttributeType extends EnumEntry
object AttributeType extends Enum[AttributeType] with CirceEnum[AttributeType] {

 val values: immutable.IndexedSeq[AttributeType] = findValues

  case object Number extends AttributeType
  case object Text extends AttributeType
  case object Percentage extends AttributeType
}

Upvotes: 2

Related Questions