Scala 2.12复杂嵌套JSON反序列化至Scala对象方案咨询
解决Scala 2.12中嵌套JSON反序列化到Case Class的问题(含验证逻辑)
针对你的问题,我会先给出Lift-JSON的完整实现方案,再提供一个更现代的轻量替代方案(Circe),两者都不会引入Play重型依赖,同时包含你需要的验证逻辑。
方案一:使用Lift-JSON实现
首先确保你的项目中引入了Lift-JSON的依赖(Scala 2.12对应版本),在sbt中添加:
libraryDependencies += "net.liftweb" %% "lift-json" % "3.4.3"
接下来是完整的代码实现,包含解析、反序列化和验证:
import net.liftweb.json._ import scala.io.Source import scala.util.Try // 你的Case Class定义 case class Bar(whistle: Integer, feather: Boolean) case class MyConfig(fizz: String, foo: Array[String], bar: Bar) def loadConfig(configFilePath: String): MyConfig = { // 初始化Lift-JSON的默认格式规则 implicit val formats: Formats = DefaultFormats // 读取并解析JSON,处理格式错误异常 val parsedJson = Try { val configJson = Source.fromFile(configFilePath).mkString parse(configJson) }.recover { case ex: Exception => throw new IllegalArgumentException(s"JSON文件解析失败:${ex.getMessage}", ex) }.get // 反序列化为MyConfig实例,处理字段/类型不匹配异常 val config = Try { parsedJson.extract[MyConfig] }.recover { case ex: MappingException => throw new IllegalArgumentException(s"JSON结构与MyConfig不匹配:${ex.getMessage}", ex) }.get // 执行自定义业务验证 validateConfig(config) config } // 自定义验证函数,可根据需求扩展规则 private def validateConfig(config: MyConfig): Unit = { val errors = scala.collection.mutable.ListBuffer[String]() // 验证fizz非空 if (config.fizz.trim.isEmpty) { errors += "fizz字段不能为空字符串" } // 验证foo数组非空且元素有效 if (config.foo.isEmpty) { errors += "foo数组不能为空" } else { config.foo.zipWithIndex.foreach { case (item, idx) => if (item.trim.isEmpty) { errors += s"foo数组第${idx+1}个元素不能为空字符串" } } } // 验证bar的whistle值合法(示例:必须大于0) if (config.bar.whistle <= 0) { errors += "bar.whistle必须大于0" } // 收集到错误则抛出异常 if (errors.nonEmpty) { throw new IllegalArgumentException(s"配置验证失败:${errors.mkString("; ")}") } }
关键说明:
DefaultFormats是Lift-JSON提供的默认序列化/反序列化规则,支持基本类型、数组、嵌套Case Class等常见结构。parse会在JSON格式错误时抛出ParsingException,extract会在字段缺失、类型不匹配时抛出MappingException,我们用Try捕获这些底层异常并包装成更友好的业务异常。- 自定义的
validateConfig函数可以灵活添加任意业务规则,最后统一抛出验证失败的异常。
方案二:使用Circe(更现代的轻量JSON库)
如果你觉得Lift-JSON的API不够直观,Circe是一个更现代、功能丰富且轻量的JSON库,同样不需要依赖Play核心。
首先添加Circe的依赖(Scala 2.12对应版本):
libraryDependencies ++= Seq( "io.circe" %% "circe-core" % "0.14.6", "io.circe" %% "circe-generic" % "0.14.6", "io.circe" %% "circe-parser" % "0.14.6" )
然后是完整实现:
import io.circe._ import io.circe.generic.semiauto._ import io.circe.parser._ import scala.io.Source // 你的Case Class定义 case class Bar(whistle: Integer, feather: Boolean) case class MyConfig(fizz: String, foo: Array[String], bar: Bar) // 自动推导解码器,无需手动编写映射规则 implicit val barDecoder: Decoder[Bar] = deriveDecoder[Bar] implicit val myConfigDecoder: Decoder[MyConfig] = deriveDecoder[MyConfig] def loadConfig(configFilePath: String): MyConfig = { // 读取JSON文件内容 val configJson = Source.fromFile(configFilePath).mkString // 解析+反序列化,用for-comprehension处理链式错误 val result = for { json <- parse(configJson) config <- json.as[MyConfig] } yield config // 处理错误分支,抛出友好异常 val config = result match { case Right(c) => c case Left(ex) => throw new IllegalArgumentException(s"JSON解析或反序列化失败:${ex.getMessage}", ex) } // 执行自定义验证逻辑 validateConfig(config) config } // 自定义验证函数(和Lift-JSON方案通用) private def validateConfig(config: MyConfig): Unit = { val errors = scala.collection.mutable.ListBuffer[String]() if (config.fizz.trim.isEmpty) errors += "fizz字段不能为空字符串" if (config.foo.isEmpty) errors += "foo数组不能为空" config.foo.zipWithIndex.foreach { case (item, idx) => if (item.trim.isEmpty) errors += s"foo数组第${idx+1}个元素不能为空字符串" } if (config.bar.whistle <= 0) errors += "bar.whistle必须大于0" if (errors.nonEmpty) { throw new IllegalArgumentException(s"配置验证失败:${errors.mkString("; ")}") } }
关键说明:
- Circe使用自动推导解码器(
deriveDecoder)生成Case Class的反序列化规则,代码更简洁易维护。 parse和as方法返回Either[Error, T],通过for-comprehension可以优雅地处理链式操作的错误分支。- 验证逻辑和Lift-JSON方案完全通用,不需要额外修改。
内容的提问来源于stack exchange,提问作者hotmeatballsoup
相关产品推荐
相关产品推荐

