如何使用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
相关产品推荐
相关产品推荐

