如何根据请求参数切换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
相关产品推荐
相关产品推荐

