You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 11:00:56