I have the following case class:
final case class Camel(firstName: String, lastName: String, waterPerDay: Int)
and circe configuration:
object CirceImplicits {
import io.circe.syntax._
import io.circe.generic.semiauto._
import io.circe.{Encoder, Decoder, Json}
import io.circe.generic.extras.Configuration
implicit val customConfig: Configuration =
Configuration.default.withSnakeCaseMemberNames.withDefaults
implicit lazy val camelEncoder: Encoder[Camel] = deriveEncoder
implicit lazy val camelDecoder: Decoder[Camel] = deriveDecoder
}
It is ok, when testing against this:
val camel = Camel(firstName = "Camelbek", lastName = "Camelov", waterPerDay = 30)
private val camelJ = Json.obj(
"firstName" -> Json.fromString("Camelbek"),
"lastName" -> Json.fromString("Camelov"),
"waterPerDay" -> Json.fromInt(30)
)
"Decoder" must "decode camel types" in {
camelJ.as[Camel] shouldBe Right(camel)
}
But this test is not passing:
val camel = Camel(firstName = "Camelbek", lastName = "Camelov", waterPerDay = 30)
private val camelJ = Json.obj(
"first_name" -> Json.fromString("Camelbek"),
"last_name" -> Json.fromString("Camelov"),
"water_per_day" -> Json.fromInt(30)
)
"Decoder" must "decode camel types" in {
camelJ.as[Camel] shouldBe Right(camel)
}
How correctly configure circe in order to be able parsing json with keys in snake case?
I'm using circe version 0.10.0
Solution 1
Circe gets field names from your case class instance and traverses through JSON using cursor, tries to get the value of each field name and tries to convert it to your desirable type.
It means that your decoder won't be able to process both cases.
The solution to this problem is to write two decoders:
val decoderDerived: Decoder[Camel] = deriveDecoder
val decoderCamelSnake: Decoder[Camel] = (c: HCursor) =>
for {
firstName <- c.downField("first_name").as[String]
lastName <- c.downField("last_name").as[String]
waterPerDay <- c.downField("water_per_day").as[Int]
} yield {
Camel(firstName, lastName, waterPerDay)
}
Then you can combine these two decoders into one using Decoder#or
implicit val decoder: Decode[Camel] = decoderDerived or decoderCamelSnake
Decoder#or will try to decode using first decoder, and if it fails, then it will try out the second one.
Solution 2
If you are fine with having only camel_case input, then you might use @ConfiguredJsonCodec
from "io.circe" %% "circe-generic-extras" % circeVersion
package. Please note that to use this annotation you also need to include paradise compiler plugin.
addCompilerPlugin(
"org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full
)
@ConfiguredJsonCodec
case class User(
firstName: String,
lastName: String
)
object User {
implicit val customConfig: Configuration = Configuration.default.withSnakeCaseMemberNames
}
val userJson = User("John", "Doe").asJson
println(userJson)
// { "first_name" : "John", "last_name" : "Doe" }
val decodedUser = decode[User](userJson.toString)
println(decodedUser)
// Right(User("John", "Doe"))
Also note that you don't need to write custom decoder & encoder derivers since that Configuration does that for you.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With