From 7c080a9eb63e9159bfa82e233f4cb7d7822c64bc Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Fri, 2 Sep 2022 22:20:26 +0100 Subject: [PATCH] better support for optional params --- .../jackson/reflect/ErasureHelper.scala | 70 +++++++++++++------ .../reflect/ScalaReflectExtensions.scala | 12 +++- .../ScalaReflectExtensionsExtrasTest.scala | 2 +- 3 files changed, 61 insertions(+), 23 deletions(-) diff --git a/src/main/scala/com/github/pjfanning/jackson/reflect/ErasureHelper.scala b/src/main/scala/com/github/pjfanning/jackson/reflect/ErasureHelper.scala index 82bd903..91607a7 100644 --- a/src/main/scala/com/github/pjfanning/jackson/reflect/ErasureHelper.scala +++ b/src/main/scala/com/github/pjfanning/jackson/reflect/ErasureHelper.scala @@ -17,28 +17,10 @@ private[reflect] object ErasureHelper { def erasedOptionalPrimitives(cls: Class[_]): Map[String, Class[_]] = { try { val mirror = universe.runtimeMirror(cls.getClassLoader) - val moduleSymbol = mirror.moduleSymbol(Class.forName(cls.getName, true, Thread.currentThread.getContextClassLoader)) - val ConstructorName = "apply" - val companion: universe.Symbol = moduleSymbol.typeSignature.member(universe.TermName(ConstructorName)) - val properties = - Try(companion.asTerm.alternatives.head.asMethod.paramLists.flatten).getOrElse { - val sym = mirror.staticClass(cls.getName) - sym.selfType.members - .filterNot(_.isMethod) - .filterNot(_.isClass) - } + val properties = getReflectProps(mirror, cls) - properties.flatMap { prop: universe.Symbol => - val maybeClass: Option[Class[_]] = prop.typeSignature.typeArgs.headOption.flatMap { signature => - if (signature.typeSymbol.isClass) { - signature.typeArgs.headOption match { - case Some(typeArg) => Option(mirror.runtimeClass(nestedTypeArg(typeArg))) - case _ => Option(mirror.runtimeClass(signature)) - } - } else { - None - } - } + properties.flatMap { prop => + val maybeClass = getPropClass(mirror, prop) val mapping: Option[(String, Class[_])] = maybeClass.flatMap { innerClass => if (innerClass.isPrimitive) { Some(prop.name.toString.trim -> innerClass) @@ -61,6 +43,52 @@ private[reflect] object ErasureHelper { } } + def getParamReferenceType(cls: Class[_], propertyName: String): Option[Class[_]] = { + try { + val mirror = universe.runtimeMirror(cls.getClassLoader) + val properties = getReflectProps(mirror, cls) + val propOpt = properties.find { prop: universe.Symbol => + prop.name.toString.trim == propertyName + } + propOpt.flatMap { prop => getPropClass(mirror, prop) } + } catch { + case NonFatal(t) => { + if (logger.isDebugEnabled) { + //use this form because of Scala 2.11 & 2.12 compile issue + logger.debug(s"Unable to get type info ${Option(cls.getName).getOrElse("null")}", t) + } else { + logger.info("Unable to get type info {}", Option(cls.getName).getOrElse("null")) + } + None + } + } + } + + private def getReflectProps(mirror: universe.Mirror, cls: Class[_]): Iterable[universe.Symbol] = { + val moduleSymbol = mirror.moduleSymbol(Class.forName(cls.getName, true, Thread.currentThread.getContextClassLoader)) + val ConstructorName = "apply" + val companion: universe.Symbol = moduleSymbol.typeSignature.member(universe.TermName(ConstructorName)) + Try(companion.asTerm.alternatives.head.asMethod.paramLists.flatten).getOrElse { + val sym = mirror.staticClass(cls.getName) + sym.selfType.members + .filterNot(_.isMethod) + .filterNot(_.isClass) + } + } + + private def getPropClass(mirror: universe.Mirror, prop: universe.Symbol): Option[Class[_]] = { + prop.typeSignature.typeArgs.headOption.flatMap { signature => + if (signature.typeSymbol.isClass) { + signature.typeArgs.headOption match { + case Some(typeArg) => Option(mirror.runtimeClass(nestedTypeArg(typeArg))) + case _ => Option(mirror.runtimeClass(signature)) + } + } else { + None + } + } + } + @tailrec private def nestedTypeArg(typeArg: universe.Type): universe.Type = { typeArg.typeArgs.headOption match { diff --git a/src/main/scala/com/github/pjfanning/jackson/reflect/ScalaReflectExtensions.scala b/src/main/scala/com/github/pjfanning/jackson/reflect/ScalaReflectExtensions.scala index afc462b..97b8895 100644 --- a/src/main/scala/com/github/pjfanning/jackson/reflect/ScalaReflectExtensions.scala +++ b/src/main/scala/com/github/pjfanning/jackson/reflect/ScalaReflectExtensions.scala @@ -14,6 +14,9 @@ import java.net.URL import scala.reflect.ClassTag object ScalaReflectExtensions { + private val OptionClass = classOf[Option[_]] + private val IterableClass = classOf[Iterable[_]] + def ::(o: JsonMapper): JsonMapper with ScalaReflectExtensions = new JsonMapperMixin(o) def ::(o: ObjectMapper): ObjectMapper with ScalaReflectExtensions = new ObjectMapperMixin(o) @@ -36,7 +39,14 @@ object ScalaReflectExtensions { val newSet = registered + cls beanDesc.properties.foreach { prop => classForProperty(prop).foreach { propClass => - registerInnerTypes(propClass, newSet) + if (propClass.isAssignableFrom(OptionClass) || propClass.isAssignableFrom(IterableClass)) { + ErasureHelper.getParamReferenceType(cls, prop.name) match { + case Some(refClass) => registerInnerTypes(refClass, newSet) + case _ => + } + } else { + registerInnerTypes(propClass, newSet) + } } } } diff --git a/src/test/scala/com/github/pjfanning/jackson/reflect/ScalaReflectExtensionsExtrasTest.scala b/src/test/scala/com/github/pjfanning/jackson/reflect/ScalaReflectExtensionsExtrasTest.scala index d050699..0797b06 100644 --- a/src/test/scala/com/github/pjfanning/jackson/reflect/ScalaReflectExtensionsExtrasTest.scala +++ b/src/test/scala/com/github/pjfanning/jackson/reflect/ScalaReflectExtensionsExtrasTest.scala @@ -42,7 +42,7 @@ class ScalaReflectExtensionsExtrasTest extends AnyFlatSpec with Matchers with Be useOptionLong(v1.wrappedLong.valueLong) shouldBe 302L } - it should "deserialize WrappedOptionOptionVarLong" ignore { + it should "deserialize WrappedOptionOptionVarLong" in { val mapper = newMapperWithScalaReflectExtensions val v1 = mapper.readValue[WrappedOptionOptionVarLong]("""{"text":"myText","wrappedLong":{"valueLong":151}}""") v1 shouldBe WrappedOptionOptionVarLong("myText", Some(OptionVarLong(Some(151L))))