Scala Akka HTTP中使用Spray JSON对嵌套Seq集合编解码的最佳方案
Akka HTTP结合Spray JSON实现Bid请求响应序列化/反序列化最佳方案
错误根因
你当前运行报错的核心原因是Spray JSON提供的jsonFormatN系列方法,后缀数字N必须和对应样例类的构造参数数量完全一致,你现在所有格式定义都写了jsonFormat1,和你定义的样例类参数数量完全不匹配,所以会触发类型不匹配错误。
正确的隐式JSON格式定义
注意隐式值的定义顺序:必须先定义被依赖的样例类的JSON格式,再定义上层依赖的格式,你用到的Option、Seq、List这些集合类型,DefaultJsonProtocol已经提供了默认序列化实现,不需要额外处理。
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport import spray.json.DefaultJsonProtocol // 统一在一个Trait里管理所有JSON格式,方便后续复用 trait JsonProtocol extends SprayJsonSupport with DefaultJsonProtocol { // 先定义最底层依赖的样例类格式 implicit val geoFormat = jsonFormat1(Geo) // Geo只有1个参数,对应jsonFormat1 implicit val userFormat = jsonFormat2(User) // User有2个参数:id、geo implicit val deviceFormat = jsonFormat2(Device) // Device有2个参数:id、geo implicit val impressionFormat = jsonFormat8(Impression) // Impression共8个构造参数 implicit val siteFormat = jsonFormat2(Site) // Site有2个参数:id、domain implicit val bannerFormat = jsonFormat4(Banner) // Banner有4个参数:id、src、width、height implicit val targetingFormat = jsonFormat1(Targeting) // Targeting仅1个参数 implicit val campaignFormat = jsonFormat5(Campaign) // Campaign共5个参数 implicit val bidRequestFormat = jsonFormat5(BidRequest) // BidRequest共5个参数 implicit val bidResponseFormat = jsonFormat5(BidResponse) // BidResponse共5个参数 }
完整业务流程实现
1. 嵌套Seq转BidRequest逻辑
你需要把activeUsers嵌套Seq里的Campaign数据,按照业务规则映射为BidRequest字段,参考实现如下:
// 继承JsonProtocol拿到所有隐式格式 object BidService extends JsonProtocol { // 从Campaign生成BidRequest,可根据实际业务规则调整字段赋值 def campaignToBidRequest(campaign: Campaign): BidRequest = { BidRequest( id = campaign.id.toString, imp = Some(List(Impression( id = "imp_"+campaign.id, wmin = Some(campaign.banners.head.width), wmax = Some(campaign.banners.head.width), w = Some(campaign.banners.head.width), hmin = Some(campaign.banners.head.height), hmax = Some(campaign.banners.head.height), h = Some(campaign.banners.head.height), bidFloor = Some(campaign.bid) ))), site = Site(id = campaign.id, domain = campaign.targeting.targetedSiteIds.head), user = Some(User(id = "user_"+campaign.id, geo = Some(Geo(Some(campaign.country))))), device = None ) } // 业务计算后生成BidResponse def calculateBidResponse(bidRequest: BidRequest, campaign: Campaign): BidResponse = { BidResponse( id = "resp_"+bidRequest.id, bidRequestId = bidRequest.id, price = campaign.bid, adid = Some(campaign.id.toString), banner = Some(campaign.banners.head) ) } }
2. Akka HTTP路由中使用序列化/反序列化
import akka.actor.typed.ActorSystem import akka.actor.typed.scaladsl.Behaviors import akka.http.scaladsl.Http import akka.http.scaladsl.server.Directives._ import scala.io.StdIn object BidServer extends App with JsonProtocol { implicit val system = ActorSystem(Behaviors.empty, "bid-system") implicit val executionContext = system.executionContext val route = path("bid") { post { // 自动将请求体反序列化为BidRequest entity(as[BidRequest]) { bidRequest => // 业务计算逻辑,此处替换为实际的流量匹配规则 val matchedCampaign = activeUsers.head val bidResponse = BidService.calculateBidResponse(bidRequest, matchedCampaign) // 自动将BidResponse序列化为JSON返回 complete(bidResponse) } } } val bindingFuture = Http().newServerAt("localhost", 8080).bind(route) println(s"Server online at http://localhost:8080/\nPress RETURN to stop...") StdIn.readLine() bindingFuture.flatMap(_.unbind()).onComplete(_ => system.terminate()) }
注意事项
- 所有样例类如果后续新增/减少构造参数,必须同步修改对应的
jsonFormatN的后缀数字,否则会报编译或运行错误 - 如果有自定义字段映射需求(比如Scala字段名是驼峰,JSON要下划线格式),可以用
jsonFormat的命名版本,比如implicit val geoFormat = jsonFormat(Geo, "country_code")来自定义JSON字段名 - 对于可选字段如果序列化时不想输出null,可以自定义格式或者用
NullOptions特质扩展你的Protocol
内容的提问来源于stack exchange,提问作者Vakindu
相关产品推荐
相关产品推荐

