Skip to content

Commit

Permalink
Added default value support
Browse files Browse the repository at this point in the history
  • Loading branch information
jroper committed Dec 5, 2019
1 parent 118acfa commit ed96d11
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,10 @@ package com.fasterxml.jackson.module.scala.introspect

import scala.language.existentials

case class BeanDescriptor(beanType: Class[_], properties: Seq[PropertyDescriptor])
case class BeanDescriptor(beanType: Class[_], properties: Seq[PropertyDescriptor]) {
val constructorParameters: Map[Int, ConstructorParameter] = {
properties.collect {
case PropertyDescriptor(_, Some(param), _, _, _, _, _) => param.index -> param
}.toMap
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,30 @@ object BeanIntrospector {
val primaryConstructor = c.getConstructors.headOption
val debugCtorParamNames = primaryConstructor.toIndexedSeq.flatMap(getCtorParams)
val index = debugCtorParamNames.indexOf(name)
val companion = findCompanionObject(c)
if (index >= 0) {
Some(ConstructorParameter(primaryConstructor.get, index, None))
Some(ConstructorParameter(primaryConstructor.get, index, findConstructorDefaultValue(companion, index)))
} else {
findConstructorParam(c.getSuperclass, name)
}
}

def findConstructorDefaultValue(maybeCompanion: Option[AnyRef], index: Int): Option[() => AnyRef] = {
val methodName = "$lessinit$greater$default$" + (index + 1)
maybeCompanion.flatMap(companion => companion.getClass.getMethods.toStream.collectFirst {
case method if method.getName == methodName && method.getParameterCount == 0 =>
() => method.invoke(companion)
})
}

def findCompanionObject(c: Class[_]): Option[AnyRef] = {
try {
Some(c.getClassLoader.loadClass(c.getName + "$").getDeclaredField("MODULE$").get(null))
} catch {
case e: Exception => None
}
}

val hierarchy: Seq[Class[_]] = {
@tailrec
def next(c: Class[_], acc: List[Class[_]]): List[Class[_]] = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import com.fasterxml.jackson.module.scala.util.Implicits._

import scala.language.existentials

case class ConstructorParameter(constructor: Constructor[_], index: Int, defaultValueMethod: Option[Method])
case class ConstructorParameter(constructor: Constructor[_], index: Int, defaultValue: Option[() => AnyRef])

case class PropertyDescriptor(name: String,
param: Option[ConstructorParameter],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@ package com.fasterxml.jackson.module.scala.introspect
import java.lang.annotation.Annotation

import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.databind.{BeanDescription, DeserializationConfig, DeserializationContext}
import com.fasterxml.jackson.databind.`type`.ClassKey
import com.fasterxml.jackson.databind.deser.{CreatorProperty, NullValueProvider, SettableBeanProperty, ValueInstantiator, ValueInstantiators}
import com.fasterxml.jackson.databind.deser.std.StdValueInstantiator
import com.fasterxml.jackson.databind.introspect._
import com.fasterxml.jackson.databind.util.LRUMap
import com.fasterxml.jackson.databind.util.{AccessPattern, LRUMap}
import com.fasterxml.jackson.module.paranamer.ParanamerAnnotationIntrospector
import com.fasterxml.jackson.module.scala.JacksonModule
import com.fasterxml.jackson.module.scala.util.Implicits._

object ScalaAnnotationIntrospector extends NopAnnotationIntrospector
object ScalaAnnotationIntrospector extends NopAnnotationIntrospector with ValueInstantiators
{
private [this] val _descriptorCache = new LRUMap[ClassKey, BeanDescriptor](16, 100)

Expand Down Expand Up @@ -114,9 +117,54 @@ object ScalaAnnotationIntrospector extends NopAnnotationIntrospector
JsonCreator.Mode.PROPERTIES
} else null
}

class ScalaValueInstantiator(delegate: StdValueInstantiator, config: DeserializationConfig, descriptor: BeanDescriptor) extends StdValueInstantiator(delegate) {

private val overriddenConstructorArguments = {
val args = delegate.getFromObjectArguments(config)

args.map {
case creator: CreatorProperty =>
// Locate the constructor param that matches it
descriptor.properties.find(_.param.exists(_.index == creator.getCreatorIndex)) match {
case Some(PropertyDescriptor(name, Some(ConstructorParameter(_, _, Some(defaultValue))), _, _, _, _, _)) =>
creator.withNullProvider(new NullValueProvider {
override def getNullValue(ctxt: DeserializationContext): AnyRef = defaultValue()

override def getNullAccessPattern: AccessPattern = AccessPattern.DYNAMIC
})
case _ => creator
}
case other => other
}
}

override def getFromObjectArguments(config: DeserializationConfig): Array[SettableBeanProperty] = {
overriddenConstructorArguments
}
}

override def findValueInstantiator(config: DeserializationConfig, beanDesc: BeanDescription,
defaultInstantiator: ValueInstantiator): ValueInstantiator = {

if (isMaybeScalaBeanType(beanDesc.getBeanClass)) {

val descriptor = _descriptorFor(beanDesc.getBeanClass)
if (descriptor.properties.exists(_.param.exists(_.defaultValue.isDefined))) {
defaultInstantiator match {
case std: StdValueInstantiator =>
new ScalaValueInstantiator(std, config, descriptor)
case other =>
throw new IllegalArgumentException("Cannot customise a non StdValueInstantiatiator: " + other.getClass)
}
} else defaultInstantiator

} else defaultInstantiator
}
}

trait ScalaAnnotationIntrospectorModule extends JacksonModule {
this += { _.appendAnnotationIntrospector(new ParanamerAnnotationIntrospector()) }
this += { _.appendAnnotationIntrospector(ScalaAnnotationIntrospector) }
this += { _.addValueInstantiators(ScalaAnnotationIntrospector) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,11 @@ object CreatorTest
this(script, 0)
}
}

case class ConstructorWithDefaultValues(s: String = "some string", i: Int = 10, dummy: String)
}


@RunWith(classOf[JUnitRunner])
class CreatorTest extends DeserializationFixture {
import CreatorTest._
Expand Down Expand Up @@ -104,4 +107,14 @@ class CreatorTest extends DeserializationFixture {
val roundTrip = f.readValue[MultipleConstructorsAnn](bean)
roundTrip shouldEqual orig
}

it should "support default values" in { f =>
val deser = f.readValue[ConstructorWithDefaultValues]("""{}""")
deser.s shouldEqual "some string"
deser.i shouldEqual 10
deser.dummy shouldEqual null
val deser2 = f.readValue[ConstructorWithDefaultValues]("""{"s":"passed","i":5}""")
deser2.s shouldEqual "passed"
deser2.i shouldEqual 5
}
}

0 comments on commit ed96d11

Please sign in to comment.