diff --git a/src/main/scala/com/codahale/jerkson/deser/ScalaDeserializers.scala b/src/main/scala/com/codahale/jerkson/deser/ScalaDeserializers.scala index 862bcd2..2e2d3c8 100644 --- a/src/main/scala/com/codahale/jerkson/deser/ScalaDeserializers.scala +++ b/src/main/scala/com/codahale/jerkson/deser/ScalaDeserializers.scala @@ -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, @@ -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 diff --git a/src/main/scala/com/codahale/jerkson/deser/TupleDeserializer.scala b/src/main/scala/com/codahale/jerkson/deser/TupleDeserializer.scala new file mode 100644 index 0000000..97b3f41 --- /dev/null +++ b/src/main/scala/com/codahale/jerkson/deser/TupleDeserializer.scala @@ -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: _*) + } +} diff --git a/src/main/scala/com/codahale/jerkson/ser/ScalaSerializers.scala b/src/main/scala/com/codahale/jerkson/ser/ScalaSerializers.scala index 25c0186..51b0aed 100644 --- a/src/main/scala/com/codahale/jerkson/ser/ScalaSerializers.scala +++ b/src/main/scala/com/codahale/jerkson/ser/ScalaSerializers.scala @@ -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 { diff --git a/src/main/scala/com/codahale/jerkson/ser/TupleSerializer.scala b/src/main/scala/com/codahale/jerkson/ser/TupleSerializer.scala new file mode 100644 index 0000000..2e4a78e --- /dev/null +++ b/src/main/scala/com/codahale/jerkson/ser/TupleSerializer.scala @@ -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[_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_]], + ) +} diff --git a/src/test/scala/com/codahale/jerkson/tests/TupleSerializationSpec.scala b/src/test/scala/com/codahale/jerkson/tests/TupleSerializationSpec.scala new file mode 100644 index 0000000..ffc97a3 --- /dev/null +++ b/src/test/scala/com/codahale/jerkson/tests/TupleSerializationSpec.scala @@ -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)) + } + } +}