0

I have a custom Jackson deserializer written in scala. Structure is as follows.

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)


}

Problem is that there are too many fields to parse and obviously calling node.get() or Option(node.get) for each field is not good. I'm trying to write a generic method getValueForJsonKey() which can

  1. Parse key values and return them as either text, long, double, or date as required by the user.
  2. This method should also accept a default value which is optional. If any user provided key is missing in the JSON, default value will be returned.

Here's what i came up with

 // 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
  }
 }

This clearly doesn't work and is not the right way to do it. I'm sure there are better ways to do this. Appreciate your help.

Revision 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 Answers 2

1

Your code is actually quite close. You need to:

  1. Fix scoping (you seem to try to use local variable parsedValue outside of the method),

  2. remove .get calls (neither value nor defaultValue are Options)

  3. remove .getClass (asInstanceOf needs a type parameter and A is already a type) from the Some(value) case and remove the cast completely from the None case.

But a better way to write it would be

val parsedValue = /* what you have */    
parsedValue.getOrElse(defaultValue).asInstanceOf[A]

Note that because of type erasure, asInstanceOf[A] itself won't throw an exception if the type isn't correct, but your code already handles that part.

Sign up to request clarification or add additional context in comments.

2 Comments

I've added a revision 2 which works as expected. Made use of typetag in the reflect api. Only thing remaining is that i want to add a validation to make sure defaultValue has the same type as Aduring compile time. Can you provide some inputs on that, please?
Hi, Thanks for helping me out. 1. That was due to bad code formatting. It is actually inside the method. Updated the question. 2 & 3. Yes, you're right. Fixed it. I prefer the way you suggested.
0

Try jsoniter-scala. It has build in support of default values for case class fields, so in such cases it does not require to write custom codecs.

On the other hand it allows writing of custom codecs without need of redundant intermediate AST or/and string representations.

Also it is the most secure and efficient parser comparing to other JSON libraries.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.