Scala泛型方法转换JsonNode时Int类型转换失效问题求解
问题根因
你遇到的问题不是Scala match case语法本身的问题,是三个因素共同作用导致的:
- 泛型类型擦除后,你用
ClassTag做类型匹配时,classOf[Int]对应Scala的原生值类型Int,但你把转换后的值放到无类型的match分支里时,Int会自动装箱为Java的java.lang.Integer,和Scala的Int类型匹配失败 - 你统一对数值节点调用
asDouble再转目标类型的逻辑,本身也会丢失原始数值的类型信息,增加不必要的装箱拆箱开销 - 最后的
case v:T类型检查因为泛型擦除,实际是基于ClassTag的运行时检查,装箱后的Java数值类型和Scala原生值类型不匹配,就会全部返回None
修复后代码
import com.fasterxml.jackson.databind.JsonNode import scala.jdk.CollectionConverters._ import scala.reflect.{ClassTag, classTag} implicit class JsonNodeToScala(jsonNode: JsonNode) { def toSeq[T: ClassTag]: Seq[Option[T]] = jsonNode.elements.asScala.map(_.toScala[T]).toSeq // 如果需要直接返回无Option的序列(自动忽略null值),可以用下面这个方法 def toSeqWithoutNone[T: ClassTag]: Seq[T] = jsonNode.elements.asScala.flatMap(_.toScala[T]).toSeq def toMap[T: ClassTag]: Map[String, Option[T]] = jsonNode.fields.asScala.map(field => (field.getKey, field.getValue.toScala[T])).toMap def toScala[T: ClassTag]: Option[T] = { val targetCls = classTag[T].runtimeClass val rawResult = jsonNode match { case v if v.isNull => None case v if v.isBoolean => Some(v.asBoolean()) case v if v.isNumber => targetCls match { case c if c == classOf[Int] || c == classOf[java.lang.Integer] => Some(v.asInt()) case c if c == classOf[Long] || c == classOf[java.lang.Long] => Some(v.asLong()) case c if c == classOf[Double] || c == classOf[java.lang.Double] => Some(v.asDouble()) case c if c == classOf[Float] || c == classOf[java.lang.Float] => Some(v.asFloat()) case _ => Some(v.numberValue()) } case v if v.isTextual => Some(v.asText()) case _ => None } rawResult.flatMap { value => // 直接匹配成功的场景 if (targetCls.isInstance(value)) Some(value.asInstanceOf[T]) // 兼容Scala原生值类型和Java装箱类型的映射 else (targetCls, value) match { case (c, v: java.lang.Number) if c == classOf[Int] => Some(v.intValue().asInstanceOf[T]) case (c, v: java.lang.Number) if c == classOf[Long] => Some(v.longValue().asInstanceOf[T]) case (c, v: java.lang.Number) if c == classOf[Double] => Some(v.doubleValue().asInstanceOf[T]) case (c, v: java.lang.Number) if c == classOf[Float] => Some(v.floatValue().asInstanceOf[T]) case _ => None } } } }
效果验证
你要求的所有场景都可以正常返回结果:
new ObjectMapper().readTree("[1, 2, 3]").toSeqWithoutNone[Int]=>Seq(1, 2, 3)new ObjectMapper().readTree("[1, 2, 3]").toSeqWithoutNone[Double]=>Seq(1.0, 2.0, 3.0)new ObjectMapper().readTree("[1.0, 2.0, 3.0]").toSeqWithoutNone[Int]=>Seq(1, 2, 3)new ObjectMapper().readTree("[1.0, 2.0, 3.0]").toSeqWithoutNone[Double]=>Seq(1.0, 2.0, 3.0)
内容的提问来源于stack exchange,提问作者Lotcher
相关产品推荐
相关产品推荐

