简体   繁体   中英

Scala - How to extract Json4s with dynamic case class created with ToolBox

I defined the case class dynamically using Toolbox. And when I do extract of json4s, I get the following exception:

  import org.json4s._
  import scala.reflect.runtime._
  import scala.tools.reflect.ToolBox

  implicit val formats = DefaultFormats

  val cm = universe.runtimeMirror(getClass.getClassLoader)
  val toolBox = cm.mkToolBox()

  val parse =
    toolBox.parse(
      s"""
         | case class Person( name:String, age:String)
         | scala.reflect.classTag[ Person].runtimeClass
       """.stripMargin)

  val person = toolBox.compile( parse)().asInstanceOf[Class[_]]

  val js = JsonMethods.parse("""{ "name":"Tom","age" : "28"}""")
  val jv = js.extract[person.type ] //How do I pass the class type?

**"Exception in thread "main" org.json4s.MappingException: No constructor for type Class, JObject(List((name,JString(Tom)), (age,JString(28))))"** 

But after creating a dummy instance of the dynamically created class, Then pass in the type of that dummy class and it will be parsed.

I don't know why. How can I parse without creating a dummy instance?

  import org.json4s._
  import scala.reflect.runtime._
  import scala.tools.reflect.ToolBox

  implicit val formats = DefaultFormats

  val cm = universe.runtimeMirror(getClass.getClassLoader)
  val toolBox = cm.mkToolBox()

  val parse =
    toolBox.parse(
      s"""
         | case class Person( name:String, age:String)
         | scala.reflect.classTag[ Person].runtimeClass
       """.stripMargin)

  val person = toolBox.compile( parse)().asInstanceOf[Class[_]]
  val dummy  = person.getConstructors.head.newInstance( "a", "b") //make dummy instance

  val js = JsonMethods.parse("""{ "name":"Tom","age" : "28"}""")
  println( js.extract[ dummy.type ] ) // Result: Person(Tom,28)

x.type is a singleton type. So person.type can't be correct, it's the singleton type of this specific variable val person: Class[_] .

Fortunately, dummy.type is correct because of the runtime reflection. This works even for ordinary case class

import org.json4s._
import org.json4s.jackson.JsonMethods

implicit val formats = DefaultFormats

case class Person(name: String, age: String)

val js = JsonMethods.parse("""{ "name":"Tom","age" : "28"}""")

val dummy0: AnyRef = Person("a", "b")
val dummy: AnyRef = dummy0

js.extract[dummy.type] // Person(Tom,28)

Actually after resolving implicits js.extract[Person] is

js.extract[Person](formats, ManifestFactory.classType(classOf[Person])

js.extract[dummy.type] is

js.extract[dummy.type](formats, ManifestFactory.singleType(dummy))

So for a toolbox-generated case class we could try

import org.json4s._
import org.json4s.jackson.JsonMethods
import scala.reflect.ManifestFactory
import scala.reflect.runtime.universe
import scala.reflect.runtime.universe._
import scala.tools.reflect.ToolBox

val cm = universe.runtimeMirror(getClass.getClassLoader)
val toolBox = cm.mkToolBox()

implicit val formats = DefaultFormats

val person = toolBox.eval(q"""
  case class Person(name:String, age:String)
  scala.reflect.classTag[Person].runtimeClass
""").asInstanceOf[Class[_]]

val js = JsonMethods.parse("""{ "name":"Tom","age" : "28"}""")

js.extract(formats, ManifestFactory.classType(person))
// java.lang.ClassCastException: __wrapper$1$6246735221dc4d64a9e372a9d0891e5e.__wrapper$1$6246735221dc4d64a9e372a9d0891e5e$Person$1 cannot be cast to scala.runtime.Nothing$

( toolBox.eval(tree) is instead of toolBox.compile(toolBox.parse(string))() )

but this doesn't work.

Manifest should be captured from toolbox compile time

import org.json4s._
import org.json4s.jackson.JsonMethods
import scala.reflect.runtime.universe
import scala.reflect.runtime.universe._
import scala.tools.reflect.ToolBox

val cm = universe.runtimeMirror(getClass.getClassLoader)
val toolBox = cm.mkToolBox()

implicit val formats = DefaultFormats

val person = toolBox.eval(q"""
  case class Person(name:String, age:String)
  val clazz = scala.reflect.classTag[Person].runtimeClass
  scala.reflect.ManifestFactory.classType(clazz)
""").asInstanceOf[Manifest[_]]

val js = JsonMethods.parse("""{ "name":"Tom","age" : "28"}""")

js.extract(formats, person) // Person(Tom,28)

Alternatively you don't need java-reflection Class at all. You can do

import scala.reflect.runtime
import scala.reflect.runtime.universe._
import scala.tools.reflect.ToolBox

val cm = runtime.currentMirror
val toolBox = cm.mkToolBox()

toolBox.eval(q"""
  import org.json4s._
  import org.json4s.jackson.JsonMethods

  implicit val formats = DefaultFormats

  case class Person(name: String, age: String)

  val js = JsonMethods.parse(${"""{"name":"Tom","age" : "28"}"""})

  js.extract[Person]
""") // Person(Tom,28)

or

import org.json4s._
import org.json4s.jackson.JsonMethods
import scala.reflect.runtime
import scala.reflect.runtime.universe._
import scala.tools.reflect.ToolBox

object Main extends App {
  val cm = runtime.currentMirror
  val toolBox = cm.mkToolBox()

  implicit val formats = DefaultFormats

  val person: ClassSymbol = toolBox.define(q"case class Person(name: String, age: String)")

  val js = JsonMethods.parse("""{"name":"Tom","age" : "28"}""")

  val jv = toolBox.eval(q"""
    import Main._
    js.extract[$person]
  """)
 
  println(jv)  // Person(Tom,28)
} 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM