scalajacksoncirce

Circe alternative for Jackson PropertyNamingStrategy


We are moving from using Jackson to Circe.

To shorten the discussion I have for example the following case class:

import com.fasterxml.jackson.databind.PropertyNamingStrategies

@JsonNaming(classOf[PropertyNamingStrategies.SnakeCaseStrategy])
final case class User(
    firstName: Option[Boolean] = None,
    lastName: Option[String] = None,
)

This makes sure that the external service responding with

{
  "first_name": "John", 
  "last_name": "Doe"
}

Gets mapped properly. But I am struggling to find Circe alternative for this. I am positive there must be a built in config somewhere, I am just not reading the docs correctly


Solution

  • You can use Circe Generic Extras:

    final case class User(
        firstName: Option[Boolean] = None,
        lastName: Option[String] = None,
    )
    object User {  
      import io.circe.generic.extras.Configuration
      implicit val customConfig: Configuration =
        Configuration.default.withSnakeCaseMemberNames
    
      import io.circe.generic.extras.semiauto._
      implicit val encoder: Encoder[User] = deriveConfiguredEncoder
      implicit val decoder: Decoder[User] = deriveConfiguredDecoder
    }
    

    In Scala 2 you could reduce some bolierplate with:

    import io.circe.generic.extras.semiauto.ConfiguredJsonCodec
    
    @ConfiguredJsonCodec
    final case class User(
        firstName: Option[Boolean] = None,
        lastName: Option[String] = None,
    )
    object User {
      import io.circe.generic.extras.Configuration
      // this config can be put somewhere else if you want to share it
      // e.g. in package object of the package - but I'd add private[packagename]
      // to limit its scope to just this package
      implicit val customConfig: Configuration =
        Configuration.default.withSnakeCaseMemberNames
    }
    

    Unfortunately, as I am writing this, it hasn't been adjusted for Scala 3, I see no build-in derived method in these classes, so you cannot do just

    final case class User(
        firstName: Option[Boolean] = None,
        lastName: Option[String] = None,
    ) derives ConfiguredEncoder, ConfiguredDecoder
    object User {
      import io.circe.generic.extras.Configuration
      implicit val customConfig: Configuration =
        Configuration.default.withSnakeCaseMemberNames
    }
    

    But maybe it will be added in future so Scala 3 version could have as little boilerplate as Scala 2.