如何在Akka Scala应用中用Json4s提取并转换含日期的Case Class?
嘿,我来帮你搞定这个Akka Scala里的JSON解析问题!你要把REST接口的JSON响应映射到带Date类型的Case Class,用Circe配合Akka HTTP是个非常顺畅的方案,下面是完整的可运行实现步骤:
1. 先添加必要的依赖
首先在你的build.sbt里引入Akka HTTP、Circe以及两者的整合库,这些是实现功能的基础:
libraryDependencies ++= Seq( "com.typesafe.akka" %% "akka-http" % "10.5.0", "com.typesafe.akka" %% "akka-stream" % "2.8.5", "io.circe" %% "circe-core" % "0.14.6", "io.circe" %% "circe-generic" % "0.14.6", "io.circe" %% "circe-parser" % "0.14.6", "de.heikoseeberger" %% "akka-http-circe" % "1.39.2" )
注意:版本号可以根据你使用的Scala版本调整,确保Akka和Circe的版本兼容。
2. 定义Case Class和自定义日期解析器
Circe默认不支持java.util.Date的解析,所以我们需要自定义解码器来处理JSON里的日期字符串,同时自动生成Case Class的解析器:
import java.text.SimpleDateFormat import java.util.Date import io.circe._ import io.circe.generic.semiauto._ import de.heikoseeberger.akkahttpcirce.FailFastCirceSupport._ import akka.actor.ActorSystem import akka.http.scaladsl.Http import akka.http.scaladsl.model._ import akka.stream.ActorMaterializer import scala.concurrent.Future import scala.concurrent.duration._ // 定义匹配JSON的日期格式:"2018-02-13T16:59:59.570+0000" // 用ThreadLocal包装SimpleDateFormat,避免线程安全问题 private val dateFormat = new ThreadLocal[SimpleDateFormat] { override def initialValue(): SimpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ") } // 自定义Date类型的Circe解码器 implicit val dateDecoder: Decoder[Date] = Decoder.decodeString.emap { str => try { Right(dateFormat.get.parse(str)) } catch { case e: Exception => Left(s"Invalid date format: $str, expected format is yyyy-MM-dd'T'HH:mm:ss.SSSZ") } } // 自动生成FinishedRun的解码器(会自动复用上面的Date解码器) implicit val finishedRunDecoder: Decoder[FinishedRun] = deriveDecoder[FinishedRun] // 你的目标Case Class case class FinishedRun(id: String, createdAt: Date, finishedAt: Date)
3. 编写Akka HTTP客户端调用并解析响应
接下来是调用REST接口、获取响应并解析成FinishedRun的完整代码:
object RestClient extends App { // 初始化Akka系统和流材料器 implicit val system: ActorSystem = ActorSystem("FinishedRunClient") implicit val materializer: ActorMaterializer = ActorMaterializer() implicit val executionContext = system.dispatcher // 替换成你要调用的REST接口URL val apiUrl = "http://your-target-api/finished-run" // 发送GET请求并处理响应 val requestFuture: Future[FinishedRun] = Http().singleRequest(HttpRequest(uri = apiUrl)) .flatMap { response => response.status match { case StatusCodes.Success(_) => // 将响应实体转为严格字符串,超时时间设为5秒 response.entity.toStrict(5.seconds).map(_.data.decodeString("UTF-8")) .flatMap { jsonStr => io.circe.parser.decode[FinishedRun](jsonStr) match { case Right(run) => Future.successful(run) case Left(parseError) => Future.failed(new RuntimeException(s"JSON解析失败: ${parseError.getMessage}")) } } case otherStatus => Future.failed(new RuntimeException(s"请求失败,状态码: $otherStatus")) } } // 处理最终结果 requestFuture.onComplete { case scala.util.Success(run) => println("成功解析FinishedRun:") println(s"ID: ${run.id}") println(s"创建时间: ${dateFormat.get.format(run.createdAt)}") println(s"完成时间: ${dateFormat.get.format(run.finishedAt)}") system.terminate() case scala.util.Failure(error) => println(s"出错了: ${error.getMessage}") system.terminate() } }
4. 优化建议
如果你用的是Java 8+,非常推荐把java.util.Date换成java.time.Instant或LocalDateTime,因为Java 8的日期API是不可变且线程安全的,代码会更健壮:
import java.time.Instant import java.time.format.DateTimeFormatter // 线程安全的DateTimeFormatter private val instantFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ") implicit val instantDecoder: Decoder[Instant] = Decoder.decodeString.emap { str => try { Right(Instant.from(instantFormatter.parse(str))) } catch { case e: Exception => Left(s"Invalid instant format: $str") } } // 更新后的Case Class case class FinishedRun(id: String, createdAt: Instant, finishedAt: Instant)
内容的提问来源于stack exchange,提问作者k0pernikus
相关产品推荐
相关产品推荐

