概要
circeはScalaで使用できるJSONのパース用ライブラリです。Akka HTTP + circe + Flywayを用いた雑なHTTPサーバのサンプルと解説の記事にある通り、AkkaHTTPで使用されているイメージがあります。
今回はこのcirceで、ネストがあるclassをencodeする時に少しハマったのでメモ書きを残しておきます。
エラー内容
まず、今回JSONにencodeする対象のclassは以下とします。InitialResponse
の配下にCompanyResponse
のclassがいる構成になっています。
case class InitialResponse
(
byNpmrSortCompanies: Seq[CompanyResponse], // 売上高当期純利益率順
byEquityRatioCompanies: Seq[CompanyResponse], // 純資産比率順
byRoaCompanies: Seq[CompanyResponse], // ROA順
byRoeCompanies: Seq[CompanyResponse], // ROE順
)
case class CompanyResponse
(
companyId: String,
companyName: String,
address: String, // 住所
accountingPeriod: String, // 決算期
salesAmount: BigInt, // 売上高
netIncome: BigInt, // 当期純利益
totalAssets: BigInt, // 総資産
netAssets: BigInt, // 純資産
employees: BigInt, // 従業員数
npmr: BigDecimal, // 売上高当期純利益率
roa: BigDecimal, // ROA
roe: BigDecimal, // ROE
equityRatio: BigDecimal, // 純資産比率
salesPerEmployee: BigInt, // 一人当たり売上高
)
さて、この構成でInitialResponse
を以下のようなコードにて、JSONにencodeするとどうなるか。ビルド時にcould not find Lazy implicit value of type io.circe.generic.encoding.DerivedAsObjectEncoder
というエラーが発生します。
import akka.http.scaladsl.model.{ContentTypes, HttpEntity}
import akka.http.scaladsl.server.Directives.{complete, failWith}
import akka.http.scaladsl.server.StandardRoute
import companyInfo.model.api.{CompanyResponse, InitialResponse}
import companyInfo.useCase.CompanySearchUseCase
import io.circe.Encoder
import io.circe.generic.semiauto.deriveEncoder
import io.circe.syntax._
object CompanySearchController {
implicit val encodeInitialResponse: Encoder[InitialResponse] = deriveEncoder[InitialResponse]
def getInitialCompanyFetch(): StandardRoute = {
// InitialResponseのオブジェクトを取得(内容は割愛)
CompanySearchUseCase.getInitialDisplayCompanyInfo() match {
case Left(ex) => failWith(ex)
// InitialResponse型
case Right(initialRes) =>
// 受け取ったオブジェクトをJSONにencode
val res = initialRes.asJson
complete(HttpEntity(
ContentTypes.`application/json`,
s"$res"
))
}
}
}
対応
原因は、EncoderにInitialResponse
しか設定していないことです。ネストしているclassについては、その子のclassのEncoderも定義する必要があります。
ということで、以下のようにCompanyResponse
のEncoderを追加すれば解消します。なお、全ソースコードはこちらにあります。
import akka.http.scaladsl.model.{ContentTypes, HttpEntity}
import akka.http.scaladsl.server.Directives.{complete, failWith}
import akka.http.scaladsl.server.StandardRoute
import companyInfo.model.api.{CompanyResponse, InitialResponse}
import companyInfo.useCase.CompanySearchUseCase
import io.circe.Encoder
import io.circe.generic.semiauto.deriveEncoder
import io.circe.syntax._
object CompanySearchController {
// CompanyResponseのEncoderを追加
implicit val encodeCompanyResponse: Encoder[CompanyResponse] = deriveEncoder[CompanyResponse]
implicit val encodeInitialResponse: Encoder[InitialResponse] = deriveEncoder[InitialResponse]
def getInitialCompanyFetch(): StandardRoute = {
// InitialResponseのオブジェクトを取得(内容は割愛)
CompanySearchUseCase.getInitialDisplayCompanyInfo() match {
case Left(ex) => failWith(ex)
// InitialResponse型
case Right(initialRes) =>
// 受け取ったオブジェクトをJSONにencode
val res = initialRes.asJson
complete(HttpEntity(
ContentTypes.`application/json`,
s"$res"
))
}
}
}