Spark Shell中json4s解析含Option[原生类型]的Scala类失败求助
我之前碰到过类似因Scala版本差异引发的json4s解析问题,你的情况核心原因在于Scala 2.10与2.11的反射机制差异,再加上json4s在不同版本下对原生类型Option参数的处理逻辑不同,以及Spark 1.6自带的json4s版本对普通class的默认参数支持有限。
问题根源拆解
- Scala 2.10的反射API在处理普通class的默认参数时,远不如2.11完善。json4s的
extract方法依赖反射查找类的构造器,当类包含Option[Int]/Option[Double]这类原生类型可选参数时,Scala 2.10的反射无法正确识别默认参数的存在,直接抛出Can't find constructor for C错误。 - 而
Option[自定义类]能正常工作,是因为json4s解析自定义类时,会优先处理嵌套对象的构造逻辑,反射路径和原生类型Option不同,刚好避开了这个版本兼容坑。 - 你在sbt repl(Scala 2.11.8)中运行正常,正是因为Scala 2.11优化了反射对默认参数的处理,json4s可以正确识别普通class的构造器。
适配Spark 1.6的解决办法
1. 将普通class改为case class(最简便有效)
Scala的case class会自动生成标准化的构造器、toString、equals等方法,其构造器信息在Scala 2.10的反射中更容易被json4s识别。修改后的代码如下:
import org.json4s.JsonDSL._ import org.json4s.jackson.JsonMethods.{render,compact,pretty} import org.json4s.DefaultFormats import org.json4s.jackson.JsonMethods._ import org.json4s.{JValue, JObject} implicit val formats = DefaultFormats case class A(a: Int, aa: Int) // case class默认参数为public,可省略val case class B(b: Int, optA: Option[A] = None) case class C(b: Int, bb: Option[Int] = None) val jc: JObject = ("b" -> 5) ~ ("bb" -> 100) val c = jc.extract[C] // 现在在Spark Shell中可正常解析
即使把这些case class打包成JAR导入Spark Shell,也能正常工作,因为case class的构造器信息是标准化的。
2. 显式指定json4s版本(如果必须用普通class)
Spark 1.6自带的json4s版本通常较旧(如3.2.10),你可以在启动Spark Shell时,通过--packages参数导入适配Scala 2.10的json4s版本,比如和你sbt repl中一致的版本:
spark-shell --packages org.json4s:json4s-jackson_2.10:3.2.11
这个版本的json4s对Scala 2.10的反射兼容性更好,可能解决普通class的解析问题,但仍更推荐用case class,因为更符合Scala的最佳实践。
3. 手动添加构造器的隐式解析(进阶方案)
如果必须使用普通class,可以为类编写自定义的序列化/反序列化规则,不过这种方式较繁琐,示例如下:
import org.json4s._ class C(val b: Int, val bb: Option[Int] = None) implicit val formats = DefaultFormats + new CustomSerializer[C](format => ( { case JObject(List(("b", JInt(b)), ("bb", JInt(bb)))) => new C(b.toInt, Some(bb.toInt)) case JObject(List(("b", JInt(b)))) => new C(b.toInt, None) }, { case C(b, Some(bb)) => JObject(List(("b", JInt(b)), ("bb", JInt(bb)))) case C(b, None) => JObject(List(("b", JInt(b)))) } )) val jc: JObject = ("b" -> 5) ~ ("bb" -> 100) val c = jc.extract[C]
这种方式需要为每个类编写自定义解析逻辑,仅适合特殊场景,不推荐作为通用方案。
验证建议
优先尝试第一种方案(改为case class),这是最快捷且符合Scala开发习惯的解决方式,几乎能100%解决你的问题。如果必须保留普通class,再尝试第二种或第三种方案。
内容的提问来源于stack exchange,提问作者kmh

