具有默认值支持的Scala通用JSON解析器

时间:2018-06-23 09:02:12

标签: scala generics

我有一个用Scala编写的自定义Jackson解串器。结构如下。

override def deserialize(jp: JsonParser, ctx: DeserializationContext): Item = {

    val node: JsonNode = jp.getCodec.readTree(jp)

    // few double fields
    val usageHours = node.get("usageHours").asDouble()
    val usageUnits = node.get("usageUnits").asDouble()

    // few string fields
    val accountId = node.get("accountId").asText()
    val accountName = node.get("accountName").asText()

    // few long fields
    val cardinality = node.get("cardinality").asLong()

    // few date fields
    val endDateTime = customDeserializer.convert(node.get("startDateTime").asText())

    // few optionals with default values
    val engine = Option(node.get("engine")).map(value => value.asText()).getOrElse(Constants.Unknown)

    // case class object
    Item(usageHours, 
         usageUnits, 
         accountId, 
         accountName, 
         cardinality, 
         endDateTime, 
         engine)


}

问题是要解析的字段太多,显然为每个字段调用node.get()或Option(node.get)都是不好的。我正在尝试编写一个通用方法getValueForJsonKey()可以

  1. 解析键值,并根据用户要求将其返回为文本,长整型,双精度型或日期型。
  2. 此方法还应接受可选的默认值。如果JSON中缺少任何用户提供的密钥,则将返回默认值。

这就是我想出的

 // Basically trying to identify the return type from the default value passed by the user
 // This will not work if the user doesn’t pass a default value.

 private def parseValueForJsonKey [A] (node: JsonNode, key: String, defaultValue: A): A = {

  val parsedValue = Option(node.get(key)).map(value => {
    defaultValue match {
      case _: String => value.asText()
      case _: Double => value.asDouble()
      case _: Long => value.asLong()        
      case _: ZonedDateTime => customDateDeserializer.convert(value)
      case _ => throw new RuntimeException(s"Unsupported type. Cannot parse ${key} to other than supported types")
    }
  })

  parsedValue match{
     case Some(value) => value.asInstanceOf[A] 
     case None => defaultValue
  }
 }

这显然是行不通的,而且不是正确的方法。我相信有更好的方法可以做到这一点。感谢您的帮助。

修订版2

def parseValueForJsonKeyWithReturnType[A: TypeTag](
     node: JsonNode, 
     key: String, 
     defaultValue: Any = None
): A = {

    val parsedValue = Option(node.get(key)).map(value => {
      typeOf[A] match {
        case t if t =:= typeOf[String] =>
          value.asText()
        case t if t =:= typeOf[Double] =>
          value.asDouble()
        case t if t =:= typeOf[Long] =>
          value.asLong()
        case t if t =:= typeOf[ZonedDateTime] =>
          zonedDateTimeDeserializer.convert(value.asText())
        case _ => throw new RuntimeException(s"Parsing to ${typeOf[A]} isn't supported by custom deserializer")
      }
    })

    parsedValue.getOrElse(defaultValue).asInstanceOf[A]
}

2 个答案:

答案 0 :(得分:1)

您的代码实际上非常接近。您需要:

  1. 修正范围(您似乎尝试在方法之外使用局部变量parsedValue

  2. 删除.get个电话(valuedefaultValue都不是Option个电话)

  3. .getClass案例中删除asInstanceOfA需要类型参数,并且Some(value)已经是类型),然后从{{ 1}}。

但是更好的书写方式是

None

请注意,由于类型擦除,如果类型不正确,val parsedValue = /* what you have */ parsedValue.getOrElse(defaultValue).asInstanceOf[A] 本身不会引发异常,但是您的代码已经处理了该部分。

答案 1 :(得分:0)

尝试jsoniter-scala。它具有用于案例类字段的build in support of default values,因此在这种情况下,它不需要编写自定义编解码器。

另一方面,它允许writing of custom codecs,而无需冗余的中间AST或/和字符串表示形式。

与其他JSON库相比,它也是最多secureefficient解析器。