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

如何使用Play JSON API格式化泛型Trait为JSON?

解决Trait/多态类的JSON序列化问题

问题根源

Json.format[T]依赖目标类型的apply和unapply方法生成序列化规则,但Module是Trait(抽象类型),本身没有这些方法;再加上你需要处理不同子类的多态序列化,直接用Json.format[Module]必然报错。


解决方案

1. 为每个子类生成JSON格式

所有子类都是case class,可以直接用Json.format自动生成对应的隐式序列化规则:

import play.api.libs.json._

trait Module
case class Module1(dp: String, qu: String, us: String) extends Module
case class Module2(dp: String, qu: String) extends Module
case class Module3(dp: String) extends Module
case class EmptyModule() extends Module

// 为每个子类定义隐式JSON格式
implicit val module1Format: OFormat[Module1] = Json.format[Module1]
implicit val module2Format: OFormat[Module2] = Json.format[Module2]
implicit val module3Format: OFormat[Module3] = Json.format[Module3]
implicit val emptyModuleFormat: OFormat[EmptyModule] = Json.format[EmptyModule]

2. 为父Trait Module自定义多态JSON格式

添加类型标识字段(比如"type")区分不同子类,序列化时写入该字段,反序列化时根据字段值匹配对应子类:

implicit val moduleFormat: Format[Module] = new Format[Module] {
  // 序列化:根据实例类型写入标识字段 + 子类数据
  override def writes(module: Module): JsValue = module match {
    case m1: Module1 => Json.obj("type" -> "Module1") ++ Json.toJson(m1)
    case m2: Module2 => Json.obj("type" -> "Module2") ++ Json.toJson(m2)
    case m3: Module3 => Json.obj("type" -> "Module3") ++ Json.toJson(m3)
    case em: EmptyModule => Json.obj("type" -> "EmptyModule") ++ Json.toJson(em)
  }

  // 反序列化:根据标识字段选择对应子类的解析规则
  override def reads(json: JsValue): JsResult[Module] = {
    (json \ "type").validate[String].flatMap {
      case "Module1" => json.validate[Module1]
      case "Module2" => json.validate[Module2]
      case "Module3" => json.validate[Module3]
      case "EmptyModule" => json.validate[EmptyModule]
      case unknownType => JsError(s"无法识别的Module类型:$unknownType")
    }
  }
}

3. 修正原函数的错误

原函数存在两个明显问题:

  • 缺少接收JSON字符串的参数(你用了Json.parse(str)但str未定义)
  • 变量命名错误(modJsonString实际是Module实例,不是字符串)

修正后的函数:

def getModule(jsonStr: String, str1: String, str2: String): String = {
  val modObject = Json.parse(jsonStr).as[JsObject]
  val modInstance: Module = if(str1.nonEmpty && str2.nonEmpty){
    val dp = (modObject \ "dp").as[String]
    val qu = (modObject \ "qu").as[String]
    val us = (modObject \ "us").as[String]
    Module1(dp, qu, us)
  } else if(str1.nonEmpty){
    val dp = (modObject \ "dp").as[String]
    val qu = (modObject \ "qu").as[String]
    Module2(dp, qu)
  } else if(str2.nonEmpty){
    val dp = (modObject \ "dp").as[String]
    Module3(dp)
  } else {
    EmptyModule()
  }

  val jsonValue = Json.toJson(modInstance)
  jsonValue.toString()
}

额外说明

  • 如果只需要序列化(不需要反序列化),可以简化moduleFormat,只实现writes方法即可
  • 所有子类的隐式格式必须在父Trait的格式之前定义,否则会找不到对应的序列化规则
  • 类型标识字段的名称和值可以根据业务需求自定义,只要保证序列化和反序列化逻辑一致即可

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 15:07:28