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

如何根据请求参数切换Scala嵌套对象的隐式JSON序列化格式?

问题描述

我有一个Scala控制器方法getCars,接收可选布尔类型的notation查询参数(默认值为Some(false)),该方法返回Car对象列表。Car与嵌套的Wheel类均包含createdAt字段,类型为LocalDateTimeWithOffset。

我已在LocalDateTimeWithOffset类中定义两种JSON格式:一种序列化时在时间后添加偏移标记(如2022-10-22T00:00:00#1),另一种仅返回时间(如2022-10-22T00:00:00)。需求为:当notation参数为true时使用带标记格式,为false时使用不带标记格式,请问如何在控制器中根据参数值选择对应格式?

解决方案

核心思路是避免依赖全局可变的隐式Format(线程不安全),改为根据请求参数动态选择对应的日期格式,再生成适配的Car/Wheel序列化格式,最后手动完成JSON序列化。

步骤1:调整LocalDateTimeWithOffset的格式定义

将格式定义移至伴生对象中,避免每个实例重复创建,同时明确暴露两种格式:

case class LocalDateTimeWithOffset(dt: LocalDateTime, offset: Int)

object LocalDateTimeWithOffset {
  // 补充你的日期格式化器,例如ISO标准格式
  private val dateTimeUTCFormatter: DateTimeFormatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME

  val localDateTimeWithOffsetReads: Reads[LocalDateTimeWithOffset] = Reads.of[String].flatMap { str =>
    if (str.contains("#")) {
      val (dtStr, offsetStr) = str.splitAt(str.indexOf("#"))
      Reads.pure(LocalDateTimeWithOffset(LocalDateTime.parse(dtStr), offsetStr.drop(1).toInt))
    } else {
      Reads.pure(LocalDateTimeWithOffset(LocalDateTime.parse(str), 1))
    }
  }

  val withNotationWrites: Writes[LocalDateTimeWithOffset] = Writes { a =>
    JsString(a.dt.format(dateTimeUTCFormatter) + s"#${a.offset}")
  }

  val withoutNotationWrites: Writes[LocalDateTimeWithOffset] = Writes { a =>
    JsString(a.dt.format(dateTimeUTCFormatter))
  }

  // 对外暴露两种完整Format
  val withNotationFormat: Format[LocalDateTimeWithOffset] = Format(localDateTimeWithOffsetReads, withNotationWrites)
  val withoutNotationFormat: Format[LocalDateTimeWithOffset] = Format(localDateTimeWithOffsetReads, withoutNotationWrites)
}

步骤2:让Car/Wheel支持动态格式

修改Car和Wheel的伴生对象,提供方法根据传入的日期格式生成对应的序列化格式:

case class Car(
  name: String,
  createdAt: LocalDateTimeWithOffset,
  wheels: Seq[Wheel]
)

object Car {
  // 根据传入的日期格式,生成对应的Car序列化格式
  def format(dateTimeFormat: Format[LocalDateTimeWithOffset]): Format[Car] = {
    implicit val dtFormat = dateTimeFormat
    implicit val wheelFmt = Wheel.format(dateTimeFormat)
    Json.format[Car]
  }
}

case class Wheel(
  name: String,
  createdAt: LocalDateTimeWithOffset
)

object Wheel {
  // 根据传入的日期格式,生成对应的Wheel序列化格式
  def format(dateTimeFormat: Format[LocalDateTimeWithOffset]): Format[Wheel] = {
    implicit val dtFormat = dateTimeFormat
    Json.format[Wheel]
  }
}

步骤3:控制器中动态选择格式并序列化

在getCars方法中,根据notation参数选择日期格式,生成对应的Car格式后手动序列化:

def getCars(notation: Option[Boolean] = Some(false)) = identified.auth(hasOceanScope).async { implicit request =>
  carService.getCars().map {
    case Seq() => Response.NotFound
    case cars =>
      // 根据参数选择日期格式
      val dateTimeFormat = notation.getOrElse(false) match {
        case true => LocalDateTimeWithOffset.withNotationFormat
        case false => LocalDateTimeWithOffset.withoutNotationFormat
      }
      // 生成Car格式并手动序列化
      val carFormat = Car.format(dateTimeFormat)
      val json = Json.toJson(cars)(carFormat)
      Response.Ok(json)
  }
}

为什么不使用全局隐式var?

原代码中LocalDateTimeWithOffset的implicit var format是线程不安全的——多个并发请求同时修改这个变量时,会导致序列化格式混乱。动态生成格式的方式每个请求使用独立的格式实例,完全避免了并发问题。


内容的提问来源于stack exchange,提问作者user7964950

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 09:01:00