You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

错误原因分析

  1. 可选字段被定义为必填:JSON中的internal、range、next是可选字段,但Case Class中被定义为非可选类型,当JSON缺少这些字段时,解析器会报错。
  2. range类型不匹配:JSON中range是包含两个数值的数组(如[0, 100]),但Case Class中定义为Array[(Int, Int)](数组存储元组),结构完全不匹配,这是触发error.expected.jsarray的核心原因。
  3. timestamp类型无解析逻辑:JSON中timestamp是数值型时间戳,但默认的Json.reads无法将数值直接转换为ZonedDateTime,需要自定义解析规则。
  4. 命名不符合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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 20:09:23