Circe 从蛇形键中解析 json
Circe parse json from snake case keys
我有以下情况class:
final case class Camel(firstName: String, lastName: String, waterPerDay: Int)
和圈子配置:
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
}
没关系,测试时:
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)
}
但是这个测试没有通过:
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)
}
如何正确配置 circe 以便能够在 snake case 中使用键解析 json?
我正在使用 circe 版本 0.10.0
解决方案 1
Circe 从您的案例 class 实例中获取字段名称并使用游标遍历 JSON,尝试获取每个字段名称的值并尝试将其转换为您想要的类型。
这意味着您的解码器将无法处理这两种情况。
这个问题的解决方法是写两个解码器:
- 基本解码器(deriveEncoder 会起作用)
- 编码器,它使用 HCursor 在您的 JSON 中导航并获取蛇形键
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)
}
然后你可以使用Decoder#or
将这两个解码器合二为一
implicit val decoder: Decode[Camel] = decoderDerived or decoderCamelSnake
Decoder#or 将尝试使用第一个解码器进行解码,如果失败,则将尝试第二个解码器。
解决方案 2
如果您只接受 camel_case 输入,那么您可以使用 "io.circe" %% "circe-generic-extras" % circeVersion
包中的 @ConfiguredJsonCodec
。请注意,要使用此注释,您还需要包含 paradise 编译器插件。
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"))
另请注意,您无需编写自定义解码器和编码器派生程序,因为该配置会为您完成。
另一种没有注释的解决方案:
case class Camel(firstName: String, lastName: String)
object Camel extends AutoDerivation {
implicit val config: Configuration = Configuration.default.withSnakeCaseMemberNames
implicit val decoder: Decoder[Camel] = exportDecoder[Camel].instance
implicit val encoder: Encoder[Camel] = exportEncoder[Camel].instance
}
val json = Camel("aaa", "bbb").asJson.noSpaces
val obj = decode[Camel](json)
我有以下情况class:
final case class Camel(firstName: String, lastName: String, waterPerDay: Int)
和圈子配置:
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
}
没关系,测试时:
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)
}
但是这个测试没有通过:
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)
}
如何正确配置 circe 以便能够在 snake case 中使用键解析 json?
我正在使用 circe 版本 0.10.0
解决方案 1
Circe 从您的案例 class 实例中获取字段名称并使用游标遍历 JSON,尝试获取每个字段名称的值并尝试将其转换为您想要的类型。
这意味着您的解码器将无法处理这两种情况。
这个问题的解决方法是写两个解码器:
- 基本解码器(deriveEncoder 会起作用)
- 编码器,它使用 HCursor 在您的 JSON 中导航并获取蛇形键
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)
}
然后你可以使用Decoder#or
将这两个解码器合二为一implicit val decoder: Decode[Camel] = decoderDerived or decoderCamelSnake
Decoder#or 将尝试使用第一个解码器进行解码,如果失败,则将尝试第二个解码器。
解决方案 2
如果您只接受 camel_case 输入,那么您可以使用 "io.circe" %% "circe-generic-extras" % circeVersion
包中的 @ConfiguredJsonCodec
。请注意,要使用此注释,您还需要包含 paradise 编译器插件。
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"))
另请注意,您无需编写自定义解码器和编码器派生程序,因为该配置会为您完成。
另一种没有注释的解决方案:
case class Camel(firstName: String, lastName: String)
object Camel extends AutoDerivation {
implicit val config: Configuration = Configuration.default.withSnakeCaseMemberNames
implicit val decoder: Decoder[Camel] = exportDecoder[Camel].instance
implicit val encoder: Encoder[Camel] = exportEncoder[Camel].instance
}
val json = Camel("aaa", "bbb").asJson.noSpaces
val obj = decode[Camel](json)