Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

better support for optional params #8

Merged
merged 1 commit into from
Sep 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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))))
Expand Down