Scala json4s反序列化抽象trait报RefinedType MappingException问题
问题根因
这个报错是json4s反射机制的已知特性导致的:
- json4s反序列化时,只要碰到特质/抽象类类型的字段,就会反射扫描该类型所有实现类的所有public val成员,不管这个成员是不是case类构造器中声明的参数。
- 你在
Snowflake类构造器外定义的optionsMap,因为存储的value既有普通String,又有Option[String],Scala自动推断出的类型是Map[String, Object with Serializable],也就是报错信息里提到的RefinedType,json4s的反射模块无法解析这种交叉类型,直接抛出映射异常。 - 你之前验证Snowflake不继承Connector就能正常反序列化,是因为单独调用
extract[Snowflake]时,json4s仅处理构造器内声明的参数,不会触发对父特质的全量实现类扫描,自然碰不到这个有问题的非构造器字段。更换json4s版本、配置类型hints都无法解决问题,核心原因就是没有处理掉这个触发bug的非构造器字段。
可行修复方案
方案1(最小改动,快速生效)
给所有Connector实现类(Snowflake、MySQL、File)中不在构造器参数里的自定义val字段加@transient注解,或者改成def定义,让json4s反射时直接跳过这些字段,不把它们识别为需要反序列化的属性。
以Snowflake类为例的改法:
case class Snowflake( val username: String, val password: String, val host: String, val role: Option[String], val warehouse: Option[String], val port: Option[String], val db_name: String, val schema: Option[String], val activeModal: Option[String], val use_ssh: Int, val ssh_ip: Option[String], val ssh_port: Option[Int], val ssh_user: Option[String] ) extends Connector { // 加@transient注解避免被json4s扫描,也可以直接改成def定义 @transient val options = Map( "sfURL" -> s"${host}.snowflakecomputing.com", "sfUser" -> username, "sfPassword" -> password, "sfRole" -> role, "sfDatabase" -> db_name, "sfSchema" -> schema, "sfWarehouse" -> warehouse ) }
这个改法不需要调整你现有的自定义序列化器逻辑,改完即可直接跑通反序列化流程。
方案2(更规范,适配多Connector扩展)
如果后续需要新增更多Connector类型,在方案1的基础上补全序列化器的配置逻辑,避免formats作用域问题:
- 修正自定义SourceSerializer内的formats定义,不要在序列化器内部新建不带自定义配置的DefaultFormats,基于传入的format参数追加配置:
case object SourceSerializer extends CustomSerializer[Source](format => ( { case JObject(source) => { implicit val formats: Formats = format + DefaultFormats val uuid = (JObject(source) \ "uuid").extract[String] val connectorType = (JObject(source) \ "type").extract[String] val connectorParams = (JObject(source) \ "parameters").extract[String] connectorType match { case "snowflake" => Source(uuid, connectorType, parse(connectorParams).extract[Snowflake]) case "mysql" => Source(uuid, connectorType, parse(connectorParams).extract[MySQL]) case "file" => Source(uuid, connectorType, parse(connectorParams).extract[File]) case other => throw new MappingException(s"不支持的连接器类型: $other") } } case JNull => null }, { case op: Source => JString(op.getClass.getSimpleName.replace("$","")) } ))
- 调用反序列化时,把自定义序列化器注册到全局formats中:
implicit val formats: Formats = DefaultFormats + SourceSerializer parse(test).extract[Source]
内容的提问来源于stack exchange,提问作者eva93
相关产品推荐
相关产品推荐

