json4s中extract/decompose返回空JObject(Nil)的原因及修复方案
json4s框架
decompose()/extract()返回空对象问题排查 问题现象
执行测试代码时,预期得到包含a、b字段的JSON对象,实际输出为空对象JObject(Nil),排除依赖冲突后问题仍然存在。
问题复现代码:
implicit val formats: Formats = DefaultFormats case class StrInt( a: String = "A", b: Int = 2 ) val d1 = StrInt("a", 12) val json = decompose(d1) println(json)
运行时类路径依赖信息如下,无明显版本不匹配问题:
org.json4s:json4s-jackson_2.12:3.6.12 org.scala-lang:scala-library:2.12.15 ➡ 2.12.16 org.json4s:json4s-core_2.12:3.6.12 org.scala-lang:scala-library:2.12.15 ➡ 2.12.16 org.json4s:json4s-ast_2.12:3.6.12 org.scala-lang:scala-library:2.12.15 ➡ 2.12.16 org.json4s:json4s-scalap_2.12:3.6.12 org.scala-lang:scala-library:2.12.15 ➡ 2.12.16 com.thoughtworks.paranamer:paranamer:2.8 com.fasterxml.jackson.core:jackson-databind:2.9.10.8 com.fasterxml.jackson.core:jackson-annotations:2.9.10 com.fasterxml.jackson.core:jackson-core:2.9.10
根本原因
该问题和依赖冲突、Scala小版本差异无关,核心诱因是用于序列化的case class被定义在了方法/函数的局部作用域中。
json4s的序列化、反序列化逻辑依赖类构造器的参数名做字段映射,底层通过paranamer库+Scala反射读取字节码中存储的构造参数元数据。如果case class定义在方法内部(即局部类),Scala编译生成的字节码会擦除对应的构造参数名信息,json4s读取不到任何可序列化的字段,调用decompose()时就会直接返回无字段的JObject(Nil),调用extract()反序列化时也会因为找不到字段映射,生成全默认值的空对象。
依赖树中显示的Scala 2.12.15到2.12.16的小版本漂移属于二进制兼容范围,不会触发该问题。
修复方案
- 将需要参与json4s序列化/反序列化的
case class移动到顶层作用域,或者单例object的成员位置,禁止定义在方法、函数、匿名代码块的局部作用域内。 - 修复后可正常运行的代码示例:
import org.json4s._ import org.json4s.jackson.JsonMethods._ // case class定义在顶层作用域,不要放在main等方法内部 case class StrInt( a: String = "A", b: Int = 2 ) object JsonDemo { implicit val formats: Formats = DefaultFormats def main(args: Array[String]): Unit = { val d1 = StrInt("a", 12) val json = decompose(d1) println(json) // 正常输出:JObject(List((a,JString(a)), (b,JInt(12)))) } }
- 如果是在REPL、脚本环境中临时测试,直接将case class写在执行代码的最外层即可,不要嵌套在其他代码块中。
内容的提问来源于stack exchange,提问作者tribbloid
相关产品推荐
相关产品推荐

