diff --git a/src/main/scala/com/codahale/jerkson/deser/CaseClassDeserializer.scala b/src/main/scala/com/codahale/jerkson/deser/CaseClassDeserializer.scala index a383419..fa93e04 100644 --- a/src/main/scala/com/codahale/jerkson/deser/CaseClassDeserializer.scala +++ b/src/main/scala/com/codahale/jerkson/deser/CaseClassDeserializer.scala @@ -17,7 +17,7 @@ class CaseClassDeserializer(config: DeserializationConfig, provider: DeserializerProvider, classLoader: ClassLoader) extends JsonDeserializer[Object] { private val isSnakeCase = javaType.getRawClass.isAnnotationPresent(classOf[JsonSnakeCase]) - private val params = CaseClassSigParser.parse(javaType.getRawClass, config.getTypeFactory, classLoader).map { + private val params = CaseClassSigParser.parse(javaType, config.getTypeFactory, classLoader).map { case (name, jt) => (if (isSnakeCase) snakeCase(name) else name, jt) }.toArray private val paramTypes = params.map { _._2.getRawClass }.toList diff --git a/src/main/scala/com/codahale/jerkson/util/CaseClassSigParser.scala b/src/main/scala/com/codahale/jerkson/util/CaseClassSigParser.scala index 3895ea8..802142b 100644 --- a/src/main/scala/com/codahale/jerkson/util/CaseClassSigParser.scala +++ b/src/main/scala/com/codahale/jerkson/util/CaseClassSigParser.scala @@ -79,20 +79,23 @@ object CaseClassSigParser { } } - def parse[A](clazz: Class[A], factory: TypeFactory, classLoader: ClassLoader) = { + def parse[A](javaType: JavaType, factory: TypeFactory, classLoader: ClassLoader) = { + val clazz = javaType.getRawClass + val containedTypes: Map[String, JavaType] = ((0 until javaType.containedTypeCount).map { i => javaType.containedTypeName(i) -> javaType.containedType(i) }).toMap + findSym(clazz, classLoader).children.filter(c => c.isCaseAccessor && !c.isPrivate) .flatMap { ms => ms.asInstanceOf[MethodSymbol].infoType match { - case NullaryMethodType(t: TypeRefType) => ms.name -> typeRef2JavaType(t, factory, classLoader) :: Nil + case NullaryMethodType(t: TypeRefType) => ms.name -> typeRef2JavaType(t, factory, classLoader, containedTypes) :: Nil case _ => Nil } } } - protected def typeRef2JavaType(ref: TypeRefType, factory: TypeFactory, classLoader: ClassLoader): JavaType = { + protected def typeRef2JavaType(ref: TypeRefType, factory: TypeFactory, classLoader: ClassLoader, containedTypes: Map[String, JavaType]): JavaType = { try { if (ref.symbol.path == "scala.Array") { - val elementType = typeRef2JavaType(ref.typeArgs.head.asInstanceOf[TypeRefType], factory, classLoader) + val elementType = typeRef2JavaType(ref.typeArgs.head.asInstanceOf[TypeRefType], factory, classLoader, containedTypes) val realElementType = elementType.getRawClass.getName match { case "java.lang.Boolean" => classOf[Boolean] case "java.lang.Byte" => classOf[Byte] @@ -108,12 +111,16 @@ object CaseClassSigParser { val array = java.lang.reflect.Array.newInstance(realElementType, 0) factory.constructType(array.getClass) } else { - val klass = loadClass(ref.symbol.path, classLoader) - factory.constructParametricType( - klass, ref.typeArgs.map { - t => typeRef2JavaType(t.asInstanceOf[TypeRefType], factory, classLoader) - }: _* - ) + if(containedTypes.contains(ref.symbol.path)) { + containedTypes(ref.symbol.path) + } else { + val klass = loadClass(ref.symbol.path, classLoader) + factory.constructParametricType( + klass, ref.typeArgs.map { + t => typeRef2JavaType(t.asInstanceOf[TypeRefType], factory, classLoader, containedTypes) + }: _* + ) + } } } catch { case e: Throwable => { diff --git a/src/test/scala/com/codahale/jerkson/tests/CaseClassSupportSpec.scala b/src/test/scala/com/codahale/jerkson/tests/CaseClassSupportSpec.scala index 7a562f5..fdf5a55 100644 --- a/src/test/scala/com/codahale/jerkson/tests/CaseClassSupportSpec.scala +++ b/src/test/scala/com/codahale/jerkson/tests/CaseClassSupportSpec.scala @@ -236,4 +236,34 @@ class CaseClassSupportSpec extends Spec { )) } } + + class `A parameterized case class` { + @Test def `is parsable from a JSON object` = { + val c = parse[CaseClassWithParameter[Int]]("""{"id":"1","list":[2,3]}""") + + c.id.must(be(1)) + c.list.must(be(List(2,3))) + } + + @Test def `generates a JSON object` = { + generate(CaseClassWithParameter[Int](1, List(2, 3))).must(be( + """{"id":1,"list":[2,3]}""" + )) + } + } + + class `A multi-level parameterized case class` { + @Test def `is parsable from a JSON object` = { + val c = parse[CaseClassWithParameter[List[CaseClassWithParameter[Int]]]]("""{"id":"1","list":[[{"id":"2","list":[3]}]]}""") + + c.id.must(be(1)) + c.list.must(be(List(List(CaseClassWithParameter[Int](2,List(3)))))) + } + + @Test def `generates a JSON object` = { + generate(CaseClassWithParameter[List[CaseClassWithParameter[Int]]](1,List(List(CaseClassWithParameter[Int](2,List(3)))))).must(be( + """{"id":1,"list":[[{"id":2,"list":[3]}]]}""" + )) + } + } } diff --git a/src/test/scala/com/codahale/jerkson/tests/ExampleCaseClasses.scala b/src/test/scala/com/codahale/jerkson/tests/ExampleCaseClasses.scala index c3b666f..bc33236 100644 --- a/src/test/scala/com/codahale/jerkson/tests/ExampleCaseClasses.scala +++ b/src/test/scala/com/codahale/jerkson/tests/ExampleCaseClasses.scala @@ -72,3 +72,5 @@ case class CaseClassWithTwoConstructors(id: Long, name: String) { case class CaseClassWithSnakeCase(oneThing: String, twoThing: String) case class CaseClassWithArrays(one: String, two: Array[String], three: Array[Int]) + +case class CaseClassWithParameter[T](id: Long, list: List[T])