From ba6753d7a39b3046639d963ae9a9ebdc6e261635 Mon Sep 17 00:00:00 2001 From: Joseph Walton Date: Wed, 13 Jun 2012 17:09:13 +1000 Subject: [PATCH 1/4] Serialise Tuples as arrays rather than maps. --- .../jerkson/ser/ScalaSerializers.scala | 2 + .../jerkson/ser/TupleSerializer.scala | 42 +++++++++++++++++++ src/test/scala/TupleSerializationTest.scala | 18 ++++++++ 3 files changed, 62 insertions(+) create mode 100644 src/main/scala/com/codahale/jerkson/ser/TupleSerializer.scala create mode 100644 src/test/scala/TupleSerializationTest.scala 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..6be1cbf --- /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[_]] = 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/TupleSerializationTest.scala b/src/test/scala/TupleSerializationTest.scala new file mode 100644 index 0000000..bc8ed38 --- /dev/null +++ b/src/test/scala/TupleSerializationTest.scala @@ -0,0 +1,18 @@ +import org.junit.Test +import org.junit.Assert +import org.junit.Assert._ +import com.codahale.jerkson.Json + +class TupleSerializationTest { + @Test + def serializedAsArray(): Unit = { + val l = (1, "two", 3.0) + assertEquals("[1,\"two\",3.0]", Json.generate(l)) + } + + @Test + def parsedAsArray(): Unit = { + val expected = (1, "two", 3.0) + assertEquals(expected, Json.parse[(Int, String, Double)]("[1, \"two\", 3.0]")) + } +} From 751350a126cc97a78ce3a8a07737d867297795e2 Mon Sep 17 00:00:00 2001 From: Joseph Walton Date: Thu, 14 Jun 2012 18:41:52 +1000 Subject: [PATCH 2/4] Support deserialisation of tuples. --- .../jerkson/deser/ScalaDeserializers.scala | 3 ++ .../jerkson/deser/TupleDeserializer.scala | 38 +++++++++++++++ .../jerkson/ser/TupleSerializer.scala | 2 +- src/test/scala/TupleSerializationTest.scala | 18 ------- .../tests/TupleSerializationTest.scala | 47 +++++++++++++++++++ 5 files changed, 89 insertions(+), 19 deletions(-) create mode 100644 src/main/scala/com/codahale/jerkson/deser/TupleDeserializer.scala delete mode 100644 src/test/scala/TupleSerializationTest.scala create mode 100644 src/test/scala/com/codahale/jerkson/tests/TupleSerializationTest.scala 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/TupleSerializer.scala b/src/main/scala/com/codahale/jerkson/ser/TupleSerializer.scala index 6be1cbf..2e4a78e 100644 --- a/src/main/scala/com/codahale/jerkson/ser/TupleSerializer.scala +++ b/src/main/scala/com/codahale/jerkson/ser/TupleSerializer.scala @@ -14,7 +14,7 @@ class TupleSerializer extends JsonSerializer[Product] { } object TupleSerializer { - val allTupleClasses: List[Class[_]] = List( + val allTupleClasses: List[Class[_ <: Product]] = List( classOf[Tuple1[_]], classOf[Tuple2[_,_]], classOf[Tuple3[_,_,_]], diff --git a/src/test/scala/TupleSerializationTest.scala b/src/test/scala/TupleSerializationTest.scala deleted file mode 100644 index bc8ed38..0000000 --- a/src/test/scala/TupleSerializationTest.scala +++ /dev/null @@ -1,18 +0,0 @@ -import org.junit.Test -import org.junit.Assert -import org.junit.Assert._ -import com.codahale.jerkson.Json - -class TupleSerializationTest { - @Test - def serializedAsArray(): Unit = { - val l = (1, "two", 3.0) - assertEquals("[1,\"two\",3.0]", Json.generate(l)) - } - - @Test - def parsedAsArray(): Unit = { - val expected = (1, "two", 3.0) - assertEquals(expected, Json.parse[(Int, String, Double)]("[1, \"two\", 3.0]")) - } -} diff --git a/src/test/scala/com/codahale/jerkson/tests/TupleSerializationTest.scala b/src/test/scala/com/codahale/jerkson/tests/TupleSerializationTest.scala new file mode 100644 index 0000000..c984690 --- /dev/null +++ b/src/test/scala/com/codahale/jerkson/tests/TupleSerializationTest.scala @@ -0,0 +1,47 @@ +package com.codahale.jerkson.tests + +import org.junit.Assert.assertEquals +import org.junit.Test +import com.codahale.jerkson.Json +import com.codahale.jerkson.ParsingException + +class TupleSerializationTest { + @Test + def serializedAsArray(): Unit = { + val l = (1, "two", 3.0) + assertEquals("[1,\"two\",3.0]", Json.generate(l)) + } + + @Test + def parsedAsArrayWithAllIntegers(): Unit = { + val expected = (1, 2, 3) + assertEquals(expected, Json.parse[(Int, Int, Int)]("[1, 2, 3]")) + } + + @Test + def parsedAsArray(): Unit = { + val expected = (1, "two", 3.0) + assertEquals(expected, Json.parse[(Int, String, Double)]("[1, \"two\", 3.0]")) + } + + @Test(expected = classOf[ParsingException]) + def errorWhenTooFewFieldsForTuple(): Unit = { + Json.parse[(Int, Int)]("[1]") + } + + @Test(expected = classOf[ParsingException]) + def errorWhenTooManyFieldsForTuple(): Unit = { + Json.parse[(Int)]("[1,2]") + } + + @Test + def longestTupleLengthIsParsedWithAllIntegers(): Unit = { + 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) + + assertEquals(t, + 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)) + } +} From c189f6cb0b69c4a035fb3618724e6927c25c98f7 Mon Sep 17 00:00:00 2001 From: Joseph Walton Date: Thu, 14 Jun 2012 18:53:09 +1000 Subject: [PATCH 3/4] Adopt existing test style. --- .../tests/TupleSerializationSpec.scala | 40 ++++++++++++++++ .../tests/TupleSerializationTest.scala | 47 ------------------- 2 files changed, 40 insertions(+), 47 deletions(-) create mode 100644 src/test/scala/com/codahale/jerkson/tests/TupleSerializationSpec.scala delete mode 100644 src/test/scala/com/codahale/jerkson/tests/TupleSerializationTest.scala 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..d5d2414 --- /dev/null +++ b/src/test/scala/com/codahale/jerkson/tests/TupleSerializationSpec.scala @@ -0,0 +1,40 @@ +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 TupleSerializationTest extends Spec { + @Test def serializedAsArray() = { + Json.generate((1, "two", 3.0)).must(be("[1,\"two\",3.0]")) + } + + @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))) + } + + @Test(expected = classOf[ParsingException]) + def errorWhenTooFewFieldsForTuple() = { + Json.parse[(Int, Int)]("[1]") + } + + @Test(expected = classOf[ParsingException]) + def errorWhenTooManyFieldsForTuple() = { + Json.parse[(Int)]("[1,2]") + } + + @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)) + } +} diff --git a/src/test/scala/com/codahale/jerkson/tests/TupleSerializationTest.scala b/src/test/scala/com/codahale/jerkson/tests/TupleSerializationTest.scala deleted file mode 100644 index c984690..0000000 --- a/src/test/scala/com/codahale/jerkson/tests/TupleSerializationTest.scala +++ /dev/null @@ -1,47 +0,0 @@ -package com.codahale.jerkson.tests - -import org.junit.Assert.assertEquals -import org.junit.Test -import com.codahale.jerkson.Json -import com.codahale.jerkson.ParsingException - -class TupleSerializationTest { - @Test - def serializedAsArray(): Unit = { - val l = (1, "two", 3.0) - assertEquals("[1,\"two\",3.0]", Json.generate(l)) - } - - @Test - def parsedAsArrayWithAllIntegers(): Unit = { - val expected = (1, 2, 3) - assertEquals(expected, Json.parse[(Int, Int, Int)]("[1, 2, 3]")) - } - - @Test - def parsedAsArray(): Unit = { - val expected = (1, "two", 3.0) - assertEquals(expected, Json.parse[(Int, String, Double)]("[1, \"two\", 3.0]")) - } - - @Test(expected = classOf[ParsingException]) - def errorWhenTooFewFieldsForTuple(): Unit = { - Json.parse[(Int, Int)]("[1]") - } - - @Test(expected = classOf[ParsingException]) - def errorWhenTooManyFieldsForTuple(): Unit = { - Json.parse[(Int)]("[1,2]") - } - - @Test - def longestTupleLengthIsParsedWithAllIntegers(): Unit = { - 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) - - assertEquals(t, - 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)) - } -} From 7160202e2f3b944f032c51181d80f5bcf12b96d3 Mon Sep 17 00:00:00 2001 From: Joseph Walton Date: Sat, 28 Jul 2012 17:07:05 +1000 Subject: [PATCH 4/4] Fix tuple tests to run as part of the build. --- .../tests/TupleSerializationSpec.scala | 54 +++++++++++-------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/src/test/scala/com/codahale/jerkson/tests/TupleSerializationSpec.scala b/src/test/scala/com/codahale/jerkson/tests/TupleSerializationSpec.scala index d5d2414..ffc97a3 100644 --- a/src/test/scala/com/codahale/jerkson/tests/TupleSerializationSpec.scala +++ b/src/test/scala/com/codahale/jerkson/tests/TupleSerializationSpec.scala @@ -5,36 +5,48 @@ import com.codahale.jerkson.Json import com.codahale.jerkson.ParsingException import com.codahale.simplespec.Spec -class TupleSerializationTest extends Spec { - @Test def serializedAsArray() = { - Json.generate((1, "two", 3.0)).must(be("[1,\"two\",3.0]")) +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]")) + } } - @Test def parsedAsArrayWithAllIntegers() = { - Json.parse[(Int, Int, Int)]("[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))) + @Test def parsedAsArray() = { + Json.parse[(Int, String, Double)]("[1, \"two\", 3.0]").must(be((1, "two", 3.0))) + } } - @Test(expected = classOf[ParsingException]) - def errorWhenTooFewFieldsForTuple() = { - Json.parse[(Int, Int)]("[1]") - } + 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]") + @Test(expected = classOf[ParsingException]) + def errorWhenTooManyFieldsForTuple() = { + Json.parse[(Int)]("[1,2]") + } } - @Test def longestTupleLengthIsParsedWithAllIntegers() = { - val numbers = 1 to 22 - val str = "[" + numbers.mkString(",") + "]" + 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) + 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)) + 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)) + } } }