diff --git a/pom.xml b/pom.xml
index bbf5c42..47b7fa5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -10,7 +10,7 @@
com.codahale
jerkson_2.9.1
- 0.6.0-SNAPSHOT
+ 0.6.0-Matygo
Jerkson for Scala
@@ -47,8 +47,9 @@
- repo.codahale.com
- scp://codahale.com/home/codahale/repo.codahale.com
+ matygo
+ Matygo Releases
+ http://office.matygo.com:8081/nexus/content/repositories/matygo/
diff --git a/src/main/scala/com/codahale/jerkson/deser/CaseClassDeserializer.scala b/src/main/scala/com/codahale/jerkson/deser/CaseClassDeserializer.scala
index 1653292..e866866 100644
--- a/src/main/scala/com/codahale/jerkson/deser/CaseClassDeserializer.scala
+++ b/src/main/scala/com/codahale/jerkson/deser/CaseClassDeserializer.scala
@@ -15,7 +15,7 @@ class CaseClassDeserializer(config: DeserializationConfig,
classLoader: ClassLoader) extends JsonDeserializer[Object] {
private val isSnakeCase = javaType.getRawClass.isAnnotationPresent(classOf[JsonSnakeCase])
private val params = CaseClassSigParser.parse(javaType.getRawClass, config.getTypeFactory, classLoader).map {
- case (name, jt) => (if (isSnakeCase) snakeCase(name) else name, jt)
+ case (name, jt, defaultValue) => (if (isSnakeCase) snakeCase(name) else name, jt, defaultValue)
}.toArray
private val paramTypes = params.map { _._2.getRawClass }.toList
private val constructor = javaType.getRawClass.getConstructors.find { c =>
@@ -48,7 +48,7 @@ class CaseClassDeserializer(config: DeserializationConfig,
val node = jp.readValueAsTree[JsonNode]
val values = new ArrayBuffer[AnyRef]
- for ((paramName, paramType) <- params) {
+ for ((paramName, paramType, paramDefault) <- params) {
val field = node.get(paramName)
val tp = new TreeTraversingParser(if (field == null) NullNode.getInstance else field, jp.getCodec)
val value = if (paramType.getRawClass == classOf[Option[_]]) {
@@ -60,6 +60,12 @@ class CaseClassDeserializer(config: DeserializationConfig,
if (field != null || value != null) {
values += value
+ } else {
+ // see if a default value was supplied
+ paramDefault match {
+ case Some(v) => values += v
+ case None =>
+ }
}
diff --git a/src/main/scala/com/codahale/jerkson/util/CaseClassSigParser.scala b/src/main/scala/com/codahale/jerkson/util/CaseClassSigParser.scala
index fb66fdc..fd83734 100644
--- a/src/main/scala/com/codahale/jerkson/util/CaseClassSigParser.scala
+++ b/src/main/scala/com/codahale/jerkson/util/CaseClassSigParser.scala
@@ -51,6 +51,18 @@ object CaseClassSigParser {
protected def simpleName(klass: Class[_]) =
klass.getName.split("\\$").last
+ implicit def class2companion(clazz: Class[_]) = new {
+ def companionClass(classLoader: ClassLoader): Class[_] = {
+ val path = if (clazz.getName.endsWith("$")) clazz.getName else "%s$".format(clazz.getName)
+ Some(Class.forName(path, true, classLoader)).getOrElse {
+ throw new Error("Could not resolve clazz='%s'".
+ format(path))
+ }
+ }
+
+ def companionObject(classLoader: ClassLoader) = companionClass(classLoader).getField("MODULE$").get(null)
+ }
+
protected def findSym[A](clazz: Class[A], classLoader: ClassLoader) = {
val name = simpleName(clazz)
val pss = parseScalaSig(clazz, classLoader)
@@ -81,12 +93,26 @@ object CaseClassSigParser {
def parse[A](clazz: Class[A], factory: TypeFactory, classLoader: ClassLoader) = {
findSym(clazz, classLoader).children.filter(c => c.isCaseAccessor && !c.isPrivate)
- .flatMap { ms =>
+ .zipWithIndex.map { case (ms,idx) => {
ms.asInstanceOf[MethodSymbol].infoType match {
- case NullaryMethodType(t: TypeRefType) => ms.name -> typeRef2JavaType(t, factory, classLoader) :: Nil
+ case NullaryMethodType(t: TypeRefType) => {
+
+ // try and find the field's default
+ val companionClass = clazz.companionClass(classLoader)
+ val companionObject = clazz.companionObject(classLoader)
+ val defaultMethod = try {
+ Some(companionClass.getMethod("apply$default$%d".format(idx + 1)))
+ }
+ catch {
+ case _ => None // indicates no default value was supplied
+ }
+ val defaultValue = defaultMethod.map(m => Some(m.invoke(companionObject))).getOrElse(None)
+
+ Tuple3(ms.name, typeRef2JavaType(t, factory, classLoader), defaultValue) :: Nil
+ }
case _ => Nil
}
- }
+ }}.flatten
}
protected def typeRef2JavaType(ref: TypeRefType, factory: TypeFactory, classLoader: ClassLoader): JavaType = {
diff --git a/src/test/scala/com/codahale/jerkson/tests/CaseClassSupportSpec.scala b/src/test/scala/com/codahale/jerkson/tests/CaseClassSupportSpec.scala
index e73c498..4797266 100644
--- a/src/test/scala/com/codahale/jerkson/tests/CaseClassSupportSpec.scala
+++ b/src/test/scala/com/codahale/jerkson/tests/CaseClassSupportSpec.scala
@@ -3,8 +3,8 @@ package com.codahale.jerkson.tests
import com.codahale.jerkson.Json._
import com.codahale.simplespec.Spec
import com.codahale.jerkson.ParsingException
-import com.fasterxml.jackson.databind.node.IntNode
import org.junit.Test
+import com.fasterxml.jackson.databind.node.IntNode
class CaseClassSupportSpec extends Spec {
class `A basic case class` {
@@ -27,6 +27,13 @@ class CaseClassSupportSpec extends Spec {
}
}
+ class `A case class with a default field` {
+ @Test def `is parsable from an incomplete JSON object` = {
+ parse[CaseClassWithDefaultString]("""{"id":1}""").must(be(CaseClassWithDefaultString(1, "Coda")))
+ parse[CaseClassWithDefaultInt]("""{"id":1}""").must(be(CaseClassWithDefaultInt(1, 42)))
+ }
+ }
+
class `A case class with lazy fields` {
@Test def `generates a JSON object with those fields evaluated` = {
generate(CaseClassWithLazyVal(1)).must(be("""{"id":1,"woo":"yeah"}"""))
diff --git a/src/test/scala/com/codahale/jerkson/tests/ExampleCaseClasses.scala b/src/test/scala/com/codahale/jerkson/tests/ExampleCaseClasses.scala
index 5ce4798..f49b50c 100644
--- a/src/test/scala/com/codahale/jerkson/tests/ExampleCaseClasses.scala
+++ b/src/test/scala/com/codahale/jerkson/tests/ExampleCaseClasses.scala
@@ -1,11 +1,14 @@
package com.codahale.jerkson.tests
-import com.fasterxml.jackson.databind.JsonNode
-import com.fasterxml.jackson.annotation.{JsonIgnoreProperties, JsonIgnore}
import com.codahale.jerkson.JsonSnakeCase
+import com.fasterxml.jackson.annotation.{JsonIgnoreProperties, JsonIgnore}
+import com.fasterxml.jackson.databind.JsonNode
case class CaseClass(id: Long, name: String)
+case class CaseClassWithDefaultString(id: Long, name: String = "Coda")
+case class CaseClassWithDefaultInt(id: Long, answer: Int = 42)
+
case class CaseClassWithLazyVal(id: Long) {
lazy val woo = "yeah"
}