JSON转Case Class解析报错求助:error.expected.jsarray问题排查
解析listdescriptors接口JSON时的报错排查
报错信息
运行单元测试解析接口返回结果时触发以下错误:
Error: Error when parsing result of 'listdescriptors': {"obj":[{"msg":["error.expected.jsarray"],"args":[]}]}! [info] at org.bitcoins.rpc.client.common.Client.parseResult(Client.scala:455)
推测问题根源在于JSON到Case Class的解析逻辑存在错误。
接口返回的JSON结构
接口返回的JSON结构如下:
{ "wallet_name" : "str", "descriptors" : [ { "desc" : "str", "timestamp" : n, "active" : true|false, "internal" : true|false, "range" : [ n, n ], "next" : n }, ... ] }
其中internal、range、next为可选字段,仅在特定条件下存在。
现有代码
JsonSerializers实现
object JsonSerializers { implicit val descriptorsClassReads: Reads[descriptorsClass] = Json.reads[descriptorsClass] implicit val listDescriptorsReads: Reads[listDescriptorsResult] = Json.reads[listDescriptorsResult] }
Case Class定义
sealed abstract class WalletResult case class listDescriptorsResult( wallet_name: String, descriptors: Vector[descriptorsClass] ) extends WalletResult case class descriptorsClass( desc: String, timestamp: ZonedDateTime, active: Boolean, internal: Boolean, range: Array[(Int, Int)], next: Int ) extends WalletResult
错误原因分析
- 可选字段被定义为必填:JSON中的
internal、range、next是可选字段,但Case Class中被定义为非可选类型,当JSON缺少这些字段时,解析器会报错。 range类型不匹配:JSON中range是包含两个数值的数组(如[0, 100]),但Case Class中定义为Array[(Int, Int)](数组存储元组),结构完全不匹配,这是触发error.expected.jsarray的核心原因。timestamp类型无解析逻辑:JSON中timestamp是数值型时间戳,但默认的Json.reads无法将数值直接转换为ZonedDateTime,需要自定义解析规则。- 命名不符合Scala规范:Case Class名称使用小驼峰(如
listDescriptorsResult),不符合Scala大驼峰命名规范,虽不影响解析,但易引发混淆。
修正后的代码示例
修正后的Case Class
sealed abstract class WalletResult case class ListDescriptorsResult( wallet_name: String, descriptors: Vector[Descriptor] ) extends WalletResult case class Descriptor( desc: String, timestamp: ZonedDateTime, active: Boolean, internal: Option[Boolean], range: Option[(Int, Int)], next: Option[Int] ) extends WalletResult
修正后的JsonSerializers
object JsonSerializers { // 自定义时间戳转ZonedDateTime的解析器 implicit val zonedDateTimeReads: Reads[ZonedDateTime] = Reads { jsValue => jsValue.validate[Long].map { timestamp => ZonedDateTime.ofInstant(Instant.ofEpochSecond(timestamp), ZoneId.systemDefault()) } } // 自定义range数组转(Int, Int)元组的解析器 implicit val rangeReads: Reads[(Int, Int)] = Reads { jsValue => jsValue.validate[Array[Int]].flatMap { case Array(start, end) => JsSuccess((start, end)) case _ => JsError("Expected array with exactly two integers for range") } } implicit val descriptorReads: Reads[Descriptor] = Json.reads[Descriptor] implicit val listDescriptorsReads: Reads[ListDescriptorsResult] = Json.reads[ListDescriptorsResult] }
修正说明
- 将可选字段改为
Option[T]类型,JSON中缺少字段时会解析为None,避免解析失败。 - 为
range字段自定义解析器,将长度为2的数值数组转换为元组,匹配JSON结构。 - 为
timestamp字段自定义解析器,将数值型时间戳转换为ZonedDateTime。 - 修正Case Class名称为大驼峰,符合Scala命名规范。
内容的提问来源于stack exchange,提问作者Grey McCarthy
相关产品推荐
相关产品推荐

