Play框架中sealed抽象类实现Json反序列化隐式Reads的方法
问题原因
Play JSON 提供的自动宏派生工具Json.reads无法处理子类为私有类型的密封类:
- 自动派生要求密封类的所有子类均为公开类型、且和密封类在同一编译单元内,宏才能扫描到所有子类完成派生
- 你当前的
WeekGranularity是Granularity伴生对象的私有类,宏无法识别所以抛出no known subclasses错误
正确实现方案
你当前的Writes逻辑是直接将Granularity实例序列化为对应的名称字符串,只需要配套实现对应逻辑的手动Reads即可,不需要依赖自动派生:
步骤1:在Granularity伴生对象中实现序列化逻辑
把读写逻辑统一定义在类型伴生对象中,无需每次使用时单独声明:
import play.api.libs.json._ sealed abstract class Granularity(name: String, windowSize: String) { // 原有属性和方法 } object Granularity { final private case class WeekGranularity(name: String, windowSize: Int) extends Granularity("week", "'7' day") { // 确保toString返回和序列化值一致的字符串,这里示例返回"week" override def toString: String = name } val Week: Granularity = WeekGranularity(name = "week", windowSize = 1) // 后续新增其他粒度直接加对应val即可 // val Day: Granularity = DayGranularity(name = "day", windowSize = 1) // 手动实现Reads:读取JSON字符串,匹配对应Granularity实例 implicit val granularityReads: Reads[Granularity] = Reads.stringReads.collect( JsonValidationError("不支持的粒度类型") ) { case "week" => Week // 新增粒度后在这里加匹配分支即可 // case "day" => Day } // 原有Writes也可以移到这里统一管理 implicit val granularityWrites: Writes[Granularity] = Writes(granularity => JsString(granularity.toString)) // 也可以直接用Format整合读写逻辑 implicit val granularityFormat: Format[Granularity] = Format(granularityReads, granularityWrites) }
步骤2:自动派生Meta的序列化实例
Granularity的隐式序列化实例定义在伴生对象后,Play JSON可以自动识别,直接派生Meta的序列化实例即可:
import play.api.libs.json._ case class Meta(granularity: Granularity) object Meta { // 自动派生时会自动读取Granularity伴生对象的隐式实例 implicit val metaFormat: OFormat[Meta] = Json.format[Meta] }
配置完成后测试代码中不需要额外声明隐式实例,直接调用asOpt[Meta]即可正常反序列化。
可选方案:用自动派生处理密封类
如果后续子类多、不想手动维护匹配分支,可以调整结构适配自动派生规则:
- 将私有子类改为公开类型,放在
Granularity伴生对象内 - 配合
Json.config配置密封类的序列化规则,比如用字段名作为鉴别器、或者直接用子类名作为序列化值
不过对于你当前的简单字符串序列化场景,手动实现Reads的成本更低、性能也更好。
内容的提问来源于stack exchange,提问作者Ranveer Singh
相关产品推荐
相关产品推荐

