在scala中创建从字符串到类类型的映射

时间:2017-12-15 22:05:10

标签: json scala

我有很多不同的外部JSON实体,我想通过json4s(scala)解析到不同的内部案例类。通过json4s的提取功能,一切正常。我已经实现了一个parse函数,它接受一个类型和一个json字符串,并将字符串解析为类型/ case类。为了将正确的json字符串映射到正确的case类,我实现了一个模式匹配函数,它看起来像这个

entityName match {
 case "entity1" => JsonParser.parse[Entity1](jsonString)
 case "entity2" => JsonParser.parse[Entity2](jsonString)
 ....

我不喜欢这里的重复,并希望通过这样的地图进行映射:

val mapping = Map(
 "entity1" -> Entity1,
 "entity2" -> Entity2
 ....

有了这个地图,我可以像这样只执行一次JsonParser.parse函数

JsonParser.parse[mapping(entityName)](jsonString)

这不起作用,因为地图引用了对象而不是类类型。我也尝试过classOf [Entity1],但这也行不通。有没有办法做到这一点?

谢谢!

2 个答案:

答案 0 :(得分:1)

Scala无法实现JsonParser.parse工作的方式。 Scala是一种强大且静态类型的语言。这意味着编译器应该在编译时知道值的类型,以便能够验证您是否只访问它们上的有效字段和方法和/或将它们作为有效参数传递给方法。假设你的课程是

case class Entity1(value:Int, unique1:Int)
case class Entity2(value:String, unique2:String)

你写了

val parsed = JsonParser.parse[mapping("entity1")](jsonString)

编译器如何知道parsed的类型以了解parsed.value的类型或知道parsed.unique1是有效字段而parsed.unique2不是?最好的类型编译器可以分配给这样的parsed是非常通用的,如Any。当然,您可以稍后将Any转发给特定类型,但这意味着您仍然必须在asInstanceOf中明确指定该类型,这种类型会破坏整个目的。不过,如果以某种方式返回Any对你来说没问题,你可以尝试这样做:

import org.json4s.jackson.JsonMethods
implicit val formats = org.json4s.DefaultFormats // or whatever Formats you actually need

val typeMap: Map[String, scala.reflect.Manifest[_]] = Map(
  "String" -> implicitly[scala.reflect.Manifest[String]],
  "Int" -> implicitly[scala.reflect.Manifest[Int]]
)

def parseAny(typeName: String, jsonString: String): Any = {
  val jValue = JsonMethods.parse(jsonString)
  jValue.extract(formats, typeMap(typeName))
}

然后做这样的事情:

def testParseByTypeName(typeName: String, jsonString: String): Unit = {
  try {
    val parsed = parseAny(typeName, jsonString)
    println(s"parsed by name $typeName => ${parsed.getClass} - '$parsed'")
  } catch {
    case e => println(e)
  }
}

def test() = {
  testParseByTypeName("String", "\"abc\"")
  testParseByTypeName("Int", "123")
}

P.S。如果您的entityName并非来自外部(即您不会分析数据以找出实际类型),那么您根本不需要它。使用类型(不需要match / case)就足够了,例如:

def parse[T](jsonString: String)(implicit mf: scala.reflect.Manifest[T]): T = {
  val jValue = JsonMethods.parse(jsonString)
  jValue.extract[T]
}

def testParse[T](prefix: String, jsonString: String)(implicit mf: scala.reflect.Manifest[T]): Unit = {
  try {
    val parsed = parse[T](jsonString)
    println(s"$prefix => ${parsed.getClass} - '$parsed'")
  } catch {
    case e => println(e)
  }
}

def test() = {
  testParse[String]("parse String", "\"abc\"")
  testParse[Int]("parse Int", "123")
}

答案 1 :(得分:0)

从@SergGr获取想法,作为粘贴在Ammonite REPL上的代码片段:

{
    import $ivy.`org.json4s::json4s-native:3.6.0-M2`
    import org.json4s.native.JsonMethods.parse
    import org.json4s.DefaultFormats
    import org.json4s.JValue

    case class Entity1(name : String, value : Int)
    case class Entity2(name : String, value : Long)

    implicit val formats = DefaultFormats
    def extract[T](input : JValue)(implicit m : Manifest[T]) = input.extract[T]

    val mapping: Map[String, Manifest[_]] = Map(
        "entity1" -> implicitly[Manifest[Entity1]],
        "entity2" -> implicitly[Manifest[Entity2]]
      )

    val input = parse(""" { "name" : "abu", "value" : 1 } """)
    extract(input)(mapping("entity1")) //Entity1("abu", 1)
    extract(input)(mapping("entity2")) //Entity2("abu", 1L)
  }