Scala中泛型类型获取

背景

笔者最近尝试使用case class作为ActiveMQ消息的定义。同时实现,在消息发送前,将case class实例对象序列化成Json字符串;在消息接收到后,可以将消息中的Json字符串反序列化为cass class对象。上述工作抽象成trait来实现,让每一个case class定义的消息都混入该trait,以实现代码复用。由于使用scala编码,所以Json的序列化和反序列化使用的json4s组件。

case class Person为序列化后发送的消息,object Person负责将Json字符串反序列化为case class Person对象。它们分别混入了序列化和反序列化的trait,TransformToJsonTransformToObj.

消息体

package io.neal.json.message

import io.neal.json.{TransformToJson, TransformToObj}

case class Person(name: String, age: Int, email: String) extends TransformToJson
object Person extends TransformToObj[Person]

序列化trait

import org.json4s._
import org.json4s.native.Serialization
import org.json4s.native.Serialization.{write => jWrite}

trait TransformToJson {
  def toJson: String = {
    implicit val format: AnyRef with Formats= Serialization.formats(NoTypeHints)
    jWrite(this)
  }
}

反序列化trait

import org.json4s._
import org.json4s.native.Serialization
import org.json4s.native.Serialization.{read => jRead}

trait TransformToObj[A] {
  def toObj(json: String): A = {
    implicit val format: AnyRef with Formats= Serialization.formats(NoTypeHints)
    jRead[A](json)
  }
}

测试用例

package io.neal.json.message

import org.scalatest.FlatSpec

class PersonTest extends FlatSpec {
  private val personObj = Person("neal", 4, "[email protected]")
  private val personJson ="""{"name":"neal","age":4,"email":"[email protected]"}"""

  "Instance of Person converts to json string" should "be ok" in {
    assertResult(personJson)(personObj.toJson)
  }

  "Json String converts to instance of Person" should "be ok" in {
    assertResult(personObj)(Person.toObj(personJson))
  }
}

问题

运行测试用例出现以下编译错误

Error:(11, 13) No Manifest available for A.
    jRead[A](json)
Error:(11, 13) not enough arguments for method read: (implicit formats: org.json4s.Formats, implicit mf: scala.reflect.Manifest[A])A.
Unspecified value parameter mf.
    jRead[A](json)

如果将TransformToObj中的jRead[A](json)修改为jRead(json),编译可以通过,但第二条测试用例抛出异常

Parsed JSON values do not match with class constructor
args=
arg types=
executable=Executable(Constructor(public scala.runtime.Nothing$()))
cause=null
types comparison result=
org.json4s.package$MappingException: Parsed JSON values do not match with class constructor
args=
arg types=
executable=Executable(Constructor(public scala.runtime.Nothing$()))
cause=null
types comparison result=
    at org.json4s.reflect.package$.fail(package.scala:95)
    at org.json4s.Extraction$ClassInstanceBuilder.instantiate(Extraction.scala:615)
    at org.json4s.Extraction$ClassInstanceBuilder.result(Extraction.scala:639)
    at org.json4s.Extraction$.$anonfun$extract$10(Extraction.scala:409)
    at org.json4s.Extraction$.$anonfun$customOrElse$1(Extraction.scala:646)
    ...

原因

scala的泛型类型信息,比如上述代码TransformToObj[Person]中的Person,在编译期时是存在的,但在jvm运行时TransformToObj中是得不到Person类型信息的。因此,对于jRead[A](json),由于编译器在编译时,从类型模板参数A获取不到真实的类型信息,所以编译报错。对于jRead(json),由于没有指定类型模板参数,虽然编译期时不会报错,但运行时还会由于获取不到真实类型信息,导致反序列化失败抛异常。

解决方法

明白了问题原因,那么解决的思路就比较明确了,即在编译时把消息case class的具体类型信息保留下来,并在调用TransformToObjtoObj方法时将类型信息传递给它。

方法1:参数传递类型信息

package io.neal.json

import org.json4s._
import org.json4s.native.Serialization
import org.json4s.native.Serialization.{read => jRead}
import scala.reflect.Manifest

trait TransformToObj {
  def toObj[A](json: String)(mf: Manifest[A]): A = {
    implicit val format: AnyRef with Formats= Serialization.formats(NoTypeHints)
    jRead(json)(format, mf)
  }
}
package io.neal.json.message

import io.neal.json.{TransformToJson, TransformToObj}

case class Person(name: String, age: Int, email: String) extends TransformToJson
object Person extends TransformToObj {
  def toObj(json: String): Person = super.toObj[Person](json)(manifest[Person])
}

方法2:覆盖protected参数传递类型信息

package io.neal.json

import org.json4s._
import org.json4s.native.Serialization
import org.json4s.native.Serialization.{read => jRead}
import scala.reflect.Manifest

trait TransformToObj[A] {
  protected val mf: Manifest[A] = null

  def toObj(json: String): A = {
    implicit val format: AnyRef with Formats= Serialization.formats(NoTypeHints)
    jRead(json)(format, mf)
  }
}
package io.neal.json.message

import io.neal.json.{TransformToJson, TransformToObj}
import scala.reflect.Manifest

case class Person(name: String, age: Int, email: String) extends TransformToJson
object Person extends TransformToObj[Person] {
  override protected val mf: Manifest[Person] = manifest[Person]
}

你可能感兴趣的:(Scala中泛型类型获取)