如何将Akka HTTP请求实体正确反序列化为字符串数组?
问题:POST请求负载反序列化为字符串数组时出现400错误
错误信息
The request content was malformed: Expected String as JsString, but
got
{"application":{"applicationRevision":{"string":"27904"},"applicationVersion":"india","id":"132231","name":"appTest"},"guest":{"session":"12bvg","systemTime":"2021-08-24T21:19:13.282Z","visitorId":"abc"}}
发送的POST请求体
[{"application":{"applicationRevision":{"string":"27904"},"applicationVersion":"india","id":"132231","name":"appTest"},"guest":{"session":"12bvg","systemTime":"2021-08-24T21:19:13.282Z","visitorId":"abc"}}]
当前使用的路由代码
def genericEventRoute: Route = path(basePath / version / "event" / "generic") { concat( post { entity(as[Array[String]]) { inputs => extractClientIP { ip => val ipAddress = ip.toOption.map(_.getHostAddress).getOrElse("unknown") system.log.info(s"ipAddress : $ipAddress") val operationPerformed: Future[IncomingIngest.Response] = incomingIngest.ask(IncomingIngest.ConsumeGenericEvent(inputs, ipAddress, _)) onSuccess(operationPerformed) { case IncomingIngest.OK(message) => complete(StatusCodes.Created, List(`Content-Type`(`application/json`)), message) case IncomingIngest.KO(error) => complete(StatusCodes.BadRequest, error) } } } } ) }
当前仅发送不含[]的单个字符串、并使用entity(as[String])时能正常工作,但预期接收字符串数组负载,且不想通过映射全部字段的case class反序列化,希望路由处理通用的字符串数组负载。
解决方案
问题核心是:Akka HTTP默认的JSON序列化器会把请求体中的JSON对象识别为复杂结构,而非字符串,无法直接映射到Array[String]。以下两种方法无需定义case class即可实现需求:
方法1:自定义反序列化器转换JSON对象为字符串
利用Akka HTTP的Jackson支持,自定义一个FromRequestUnmarshaller,将JSON数组中的每个节点转为字符串:
import akka.http.scaladsl.unmarshalling.FromRequestUnmarshaller import com.fasterxml.jackson.databind.JsonNode import akka.http.scaladsl.marshallers.jackson.Jackson // 自定义反序列化逻辑:把JSON数组里的每个节点转成字符串 val jsonArrayToStringArray: FromRequestUnmarshaller[Array[String]] = Jackson.unmarshaller[Array[JsonNode]].map(_.map(_.toString))
修改路由,使用这个自定义的反序列化器:
def genericEventRoute: Route = path(basePath / version / "event" / "generic") { concat( post { entity(jsonArrayToStringArray) { inputs => extractClientIP { ip => val ipAddress = ip.toOption.map(_.getHostAddress).getOrElse("unknown") system.log.info(s"ipAddress : $ipAddress") val operationPerformed: Future[IncomingIngest.Response] = incomingIngest.ask(IncomingIngest.ConsumeGenericEvent(inputs, ipAddress, _)) onSuccess(operationPerformed) { case IncomingIngest.OK(message) => complete(StatusCodes.Created, List(`Content-Type`(`application/json`)), message) case IncomingIngest.KO(error) => complete(StatusCodes.BadRequest, error) } } } } ) }
方法2:读取原始请求体后手动解析
先读取整个请求体为字符串,再手动解析JSON数组,将每个对象转为字符串:
import com.fasterxml.jackson.databind.ObjectMapper def genericEventRoute: Route = path(basePath / version / "event" / "generic") { concat( post { entity(as[String]) { rawBody => val mapper = new ObjectMapper() val jsonTree = scala.util.Try(mapper.readTree(rawBody)) match { case scala.util.Success(tree) if tree.isArray => tree case _ => complete(StatusCodes.BadRequest, "请求体必须是合法的JSON数组") } val inputs = jsonTree.elements().asScala.map(_.toString).toArray extractClientIP { ip => val ipAddress = ip.toOption.map(_.getHostAddress).getOrElse("unknown") system.log.info(s"ipAddress : $ipAddress") val operationPerformed: Future[IncomingIngest.Response] = incomingIngest.ask(IncomingIngest.ConsumeGenericEvent(inputs, ipAddress, _)) onSuccess(operationPerformed) { case IncomingIngest.OK(message) => complete(StatusCodes.Created, List(`Content-Type`(`application/json`)), message) case IncomingIngest.KO(error) => complete(StatusCodes.BadRequest, error) } } } } ) }
两种方案都能将请求体中的JSON对象数组转换为字符串数组,无需定义对应case class。
内容的提问来源于stack exchange,提问作者sam.ban
相关产品推荐
相关产品推荐

