【问题标题】:Create mapping from string to class type in scala在scala中创建从字符串到类类型的映射
【发布时间】:2017-12-15 22:05:10
【问题描述】:

我有很多不同的外部 JSON 实体,我想通过 json4s (scala) 将它们解析为不同的内部案例类。通过 json4s 的提取功能一切正常。我已经实现了一个解析函数,它接受一个类型和一个 json 字符串并将字符串解析为类型/案例类。为了将正确的 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],但这也不起作用。有没有办法做到这一点?

谢谢!

【问题讨论】:

  • 嗯...你把运行时的东西和编译时的东西混在一起了。

标签: json scala


【解决方案1】:

您希望 JsonParser.parse 工作的方式在 Scala 中是不可能的。 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")
}

附:如果您的 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")
}

【讨论】:

    【解决方案2】:

    遵循@SergGr 的想法,作为 sn-p 粘贴到 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)
      }
    

    【讨论】:

    • 谢谢,结合@SergGr 的回答,这真的很有帮助。
    猜你喜欢
    • 2022-01-06
    • 1970-01-01
    • 2014-05-31
    • 1970-01-01
    • 2016-11-26
    • 2019-09-06
    • 1970-01-01
    • 1970-01-01
    • 2015-12-21
    相关资源
    最近更新 更多