Skip to content
This repository has been archived by the owner on Nov 9, 2017. It is now read-only.

#22 - better serialisation for Tuples #62

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
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 @@ -6,6 +6,7 @@ import com.codahale.jerkson.AST.{JNull, JValue}
import scala.collection.generic.{MapFactory, GenericCompanion}
import com.fasterxml.jackson.databind.deser.Deserializers
import com.fasterxml.jackson.databind.Module.SetupContext
import com.codahale.jerkson.ser.TupleSerializer

class ScalaDeserializers(classLoader: ClassLoader, context: SetupContext) extends Deserializers.Base {
override def findBeanDeserializer(javaType: JavaType, config: DeserializationConfig,
Expand Down Expand Up @@ -88,6 +89,8 @@ class ScalaDeserializers(classLoader: ClassLoader, context: SetupContext) extend
new BigDecimalDeserializer
} else if (klass == classOf[Either[_,_]]) {
new EitherDeserializer(config, javaType)
} else if (TupleSerializer.allTupleClasses.exists(_.isAssignableFrom(beanDesc.getBeanClass()))) {
new TupleDeserializer(javaType)
} else if (classOf[Product].isAssignableFrom(klass)) {
new CaseClassDeserializer(config, javaType, classLoader)
} else null
Expand Down
38 changes: 38 additions & 0 deletions src/main/scala/com/codahale/jerkson/deser/TupleDeserializer.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.codahale.jerkson.deser
import com.fasterxml.jackson.databind.JsonDeserializer
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.core.JsonToken
import com.fasterxml.jackson.databind.node.TreeTraversingParser
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.`type`.SimpleType
import com.fasterxml.jackson.databind.JavaType
import com.codahale.jerkson.ser.TupleSerializer

class TupleDeserializer(javaType: JavaType) extends JsonDeserializer[Product] {
val types = for (i <- 0 until javaType.containedTypeCount()) yield javaType.containedType(i)

def deserialize(jp: JsonParser, ctxt: DeserializationContext): Product = {
if (jp.getCurrentToken != JsonToken.START_ARRAY) {
throw ctxt.mappingException("Tuple expected as an array")
}

val builder = collection.mutable.ListBuffer[Object]()

for (t <- types) {
jp.nextToken()
val elementDeserializer = ctxt.findRootValueDeserializer(t)
builder += elementDeserializer.deserialize(jp, ctxt)
}

if (jp.nextToken() != JsonToken.END_ARRAY) {
throw ctxt.mappingException("More elements than expected for Tuple" + types.size)
}

val c: Class[_ <: Product] = TupleSerializer.allTupleClasses(types.size - 1)

val consArgs = for (n <- 1 to types.size) yield classOf[Object]

c.getConstructor(consArgs: _*).newInstance(builder.toArray: _*)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ class ScalaSerializers extends Serializers.Base {
new JValueSerializer
} else if (classOf[Either[_,_]].isAssignableFrom(beanDesc.getBeanClass)) {
new EitherSerializer
} else if (TupleSerializer.allTupleClasses.exists(_.isAssignableFrom(beanDesc.getBeanClass()))) {
new TupleSerializer
} else if (classOf[Product].isAssignableFrom(beanDesc.getBeanClass)) {
new CaseClassSerializer(beanDesc.getBeanClass)
} else {
Expand Down
42 changes: 42 additions & 0 deletions src/main/scala/com/codahale/jerkson/ser/TupleSerializer.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.codahale.jerkson.ser
import com.fasterxml.jackson.databind.JsonSerializer
import com.fasterxml.jackson.databind.SerializerProvider
import com.fasterxml.jackson.core.JsonGenerator

class TupleSerializer extends JsonSerializer[Product] {
def serialize(value: Product, json: JsonGenerator, provider: SerializerProvider) {
json.writeStartArray()
for (v <- value.productIterator.toList) {
provider.defaultSerializeValue(v, json)
}
json.writeEndArray()
}
}

object TupleSerializer {
val allTupleClasses: List[Class[_ <: Product]] = List(
classOf[Tuple1[_]],
classOf[Tuple2[_,_]],
classOf[Tuple3[_,_,_]],
classOf[Tuple4[_,_,_,_]],
classOf[Tuple5[_,_,_,_,_]],
classOf[Tuple6[_,_,_,_,_,_]],
classOf[Tuple7[_,_,_,_,_,_,_]],
classOf[Tuple8[_,_,_,_,_,_,_,_]],
classOf[Tuple9[_,_,_,_,_,_,_,_,_]],
classOf[Tuple10[_,_,_,_,_,_,_,_,_,_]],
classOf[Tuple11[_,_,_,_,_,_,_,_,_,_,_]],
classOf[Tuple12[_,_,_,_,_,_,_,_,_,_,_,_]],
classOf[Tuple13[_,_,_,_,_,_,_,_,_,_,_,_,_]],
classOf[Tuple14[_,_,_,_,_,_,_,_,_,_,_,_,_,_]],
classOf[Tuple15[_,_,_,_,_,_,_,_,_,_,_,_,_,_,_]],
classOf[Tuple16[_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_]],
classOf[Tuple17[_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_]],
classOf[Tuple18[_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_]],
classOf[Tuple19[_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_]],
classOf[Tuple20[_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_]],
classOf[Tuple21[_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_]],
classOf[Tuple22[_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_]]
// classOf[Tuple23[_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_]],
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.codahale.jerkson.tests

import org.junit.Test
import com.codahale.jerkson.Json
import com.codahale.jerkson.ParsingException
import com.codahale.simplespec.Spec

class TupleSerializationSpec extends Spec {
class `Serialization` {
@Test def serializedAsArray() = {
Json.generate((1, "two", 3.0)).must(be("[1,\"two\",3.0]"))
}

@Test def serializedAsArrayWithUniformType() = {
Json.generate((1, 2, 3)).must(be("[1,2,3]"))
}
}

class `Parsing from arrays` {
@Test def parsedAsArrayWithAllIntegers() = {
Json.parse[(Int, Int, Int)]("[1, 2, 3]").must(be((1, 2, 3)))
}

@Test def parsedAsArray() = {
Json.parse[(Int, String, Double)]("[1, \"two\", 3.0]").must(be((1, "two", 3.0)))
}
}

class `Expected parsing failures` {
@Test(expected = classOf[ParsingException])
def errorWhenTooFewFieldsForTuple() = {
Json.parse[(Int, Int)]("[1]")
}

@Test(expected = classOf[ParsingException])
def errorWhenTooManyFieldsForTuple() = {
Json.parse[(Int)]("[1,2]")
}
}

class `Parses up to largest defined tuple` {
@Test def longestTupleLengthIsParsedWithAllIntegers() = {
val numbers = 1 to 22
val str = "[" + numbers.mkString(",") + "]"

val t = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22)

Json.parse[(Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int)](str).must(
be(t))
}
}
}