如何用Play框架解析含递归密封Trait列表的JSON?
Play框架下解析JSON到Scala密封特质对象的实现方案
针对你给出的Thing密封特质、Foo/Bar样例类,以及目标JSON结构,可以基于Play JSON库通过自定义Reads来实现解析,具体步骤如下:
1. 导入依赖包
确保项目已引入Play JSON库,在代码中导入核心类:
import play.api.libs.json._ import play.api.libs.functional.syntax._
2. 自定义各类型的Reads解析器
因为Thing是密封特质,且Foo对应JSON中的原始数字类型(而非对象),需要手动实现类型判断和解析逻辑:
(1)Foo的Reads
JSON中的Foo是单个整数,直接将Int映射为Foo实例:
implicit val fooReads: Reads[Foo] = Reads.of[Int].map(Foo)
(2)Thing的Reads(处理多态分支)
通过判断JSON节点的类型,区分是Foo还是Bar:
implicit lazy val thingReads: Reads[Thing] = Reads[Thing] { case JNumber(num) => fooReads.reads(JNumber(num)) case obj: JObject => barReads.reads(obj) case other => JsError(s"无法解析为Thing类型:${other.toString()}") }
这里用lazy val解决Thing和Bar之间的循环依赖(Bar的t字段是List[Thing])。
(3)Bar的Reads
Bar是包含s和t字段的对象,可选择两种实现方式:
- 函数式语法组合(更灵活):
implicit val barReads: Reads[Bar] = ( (__ \ "s").read[String] and (__ \ "t").read[List[Thing]] )(Bar.apply)
- 自动生成解析器(字段名与类属性完全匹配时可用):
implicit val barReads: Reads[Bar] = Json.reads[Bar]
3. 执行解析操作
注意你的原始JSON需要修正为标准JSON格式(键名必须加双引号),修正后的JSON如下:
{ "s": "doomy doomy doom", "t": [ 24, { "s": "doooom!", "t": [ 1, 2, 3 ] }, 42, 126 ] }
解析代码示例:
val jsonStr = """{"s": "doomy doomy doom","t": [ 24, { "s": "doooom!", "t": [ 1, 2, 3 ] }, 42, 126 ]}""" val result: JsResult[Thing] = Json.parse(jsonStr).validate[Thing] // 处理解析结果 result match { case JsSuccess(bar: Bar, _) => println(bar) // 输出目标Bar实例 case JsError(errors) => println(s"解析失败:$errors") }
关键说明
- 必须使用标准JSON:原始JSON中的键名未加双引号,不符合JSON规范,Play JSON无法解析,需先修正。
- 多态解析逻辑:通过判断JSON节点类型(JNumber/JObject)来分支处理
Foo和Bar,确保不同结构的JSON能映射到对应的Scala类。 - 循环依赖处理:
Thing的Reads用lazy val延迟初始化,避免Bar依赖Thing导致的初始化顺序问题。
内容的提问来源于stack exchange,提问作者Jeremy
相关产品推荐
相关产品推荐

