diff --git a/src/main/java/com/codahale/jerkson/JsonNoJerkson.java b/src/main/java/com/codahale/jerkson/JsonNoJerkson.java new file mode 100644 index 0000000..168e4f9 --- /dev/null +++ b/src/main/java/com/codahale/jerkson/JsonNoJerkson.java @@ -0,0 +1,20 @@ +package com.codahale.jerkson; + +import com.fasterxml.jackson.annotation.JacksonAnnotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marker annotation which indicates that the annotated class should not be + * serialized and deserialized using jerkson, and should be passed through to + * jackson instead. + */ +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@JacksonAnnotation +public @interface JsonNoJerkson { + +} diff --git a/src/main/scala/com/codahale/jerkson/deser/ScalaDeserializers.scala b/src/main/scala/com/codahale/jerkson/deser/ScalaDeserializers.scala index 862bcd2..e71bcf0 100644 --- a/src/main/scala/com/codahale/jerkson/deser/ScalaDeserializers.scala +++ b/src/main/scala/com/codahale/jerkson/deser/ScalaDeserializers.scala @@ -3,6 +3,7 @@ package com.codahale.jerkson.deser import com.fasterxml.jackson.databind._ import scala.collection.{Traversable, MapLike, immutable, mutable} import com.codahale.jerkson.AST.{JNull, JValue} +import com.codahale.jerkson.JsonNoJerkson import scala.collection.generic.{MapFactory, GenericCompanion} import com.fasterxml.jackson.databind.deser.Deserializers import com.fasterxml.jackson.databind.Module.SetupContext @@ -11,7 +12,10 @@ class ScalaDeserializers(classLoader: ClassLoader, context: SetupContext) extend override def findBeanDeserializer(javaType: JavaType, config: DeserializationConfig, beanDesc: BeanDescription) = { val klass = javaType.getRawClass - if (klass == classOf[Range] || klass == classOf[immutable.Range]) { + val noJerkson = klass.isAnnotationPresent(classOf[JsonNoJerkson]) + if (noJerkson) { + null + } else if (klass == classOf[Range] || klass == classOf[immutable.Range]) { new RangeDeserializer } else if (klass == classOf[StringBuilder]) { new StringBuilderDeserializer diff --git a/src/main/scala/com/codahale/jerkson/ser/ScalaSerializers.scala b/src/main/scala/com/codahale/jerkson/ser/ScalaSerializers.scala index 25c0186..92b4be5 100644 --- a/src/main/scala/com/codahale/jerkson/ser/ScalaSerializers.scala +++ b/src/main/scala/com/codahale/jerkson/ser/ScalaSerializers.scala @@ -1,13 +1,17 @@ package com.codahale.jerkson.ser import com.codahale.jerkson.AST.JValue +import com.codahale.jerkson.JsonNoJerkson import com.fasterxml.jackson.databind._ import com.fasterxml.jackson.databind.ser.Serializers class ScalaSerializers extends Serializers.Base { override def findSerializer(config: SerializationConfig, javaType: JavaType, beanDesc: BeanDescription) = { - val ser: Object = if (classOf[Option[_]].isAssignableFrom(beanDesc.getBeanClass)) { - new OptionSerializer + val noJerkson = javaType.getRawClass.isAnnotationPresent(classOf[JsonNoJerkson]) + val ser: Object = if (noJerkson) { + null + } else if (classOf[Option[_]].isAssignableFrom(beanDesc.getBeanClass)) { + new OptionSerializer } else if (classOf[StringBuilder].isAssignableFrom(beanDesc.getBeanClass)) { new StringBuilderSerializer } else if (classOf[collection.Map[_,_]].isAssignableFrom(beanDesc.getBeanClass)) { diff --git a/src/test/scala/com/codahale/jerkson/tests/ExampleCaseClasses.scala b/src/test/scala/com/codahale/jerkson/tests/ExampleCaseClasses.scala index 5ce4798..7d76330 100644 --- a/src/test/scala/com/codahale/jerkson/tests/ExampleCaseClasses.scala +++ b/src/test/scala/com/codahale/jerkson/tests/ExampleCaseClasses.scala @@ -1,8 +1,8 @@ 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, JsonProperty, JsonCreator, JsonValue} +import com.codahale.jerkson.{JsonSnakeCase,JsonNoJerkson} case class CaseClass(id: Long, name: String) @@ -72,3 +72,32 @@ 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]) + +@JsonNoJerkson +case class NoJerksonCaseClass(id: Int, name: String) { + @JsonCreator def this( + @JsonProperty("ID") id: Option[Int], + @JsonProperty("name-thing") name: Option[String], + @JsonProperty("meta") meta: Any + ) = this(id.get, name.get) + + @JsonValue def toJson = Map( + "ID" -> id, + "name-thing" -> name, + "meta" -> Seq(id, name) + ) +} + +@JsonNoJerkson +class NoJerksonIterator(var count: Int) extends Iterator[Int] { + def hasNext = true + def next = { count += 1; count - 1 } + + @JsonCreator def this( + @JsonProperty("count") count: Option[Int] + ) = this(count.get) + + @JsonValue def toJson = Map( + "count" -> count + ) +} diff --git a/src/test/scala/com/codahale/jerkson/tests/NoJerksonSpec.scala b/src/test/scala/com/codahale/jerkson/tests/NoJerksonSpec.scala new file mode 100644 index 0000000..6958091 --- /dev/null +++ b/src/test/scala/com/codahale/jerkson/tests/NoJerksonSpec.scala @@ -0,0 +1,54 @@ +package com.codahale.jerkson.tests + +import com.codahale.jerkson.Json._ +import com.codahale.jerkson.ParsingException +import com.codahale.simplespec.Spec +import org.junit.Test + +class NoJerksonSpec extends Spec { + class `A no-jerkson case class` { + @Test def `generates a JSON object with matching field values` = { + generate(NoJerksonCaseClass(1, "Coda")).must(be("""{"ID":1,"name-thing":"Coda","meta":[1,"Coda"]}""")) + } + + @Test def `is parsable from a JSON object with corresponding fields` = { + parse[NoJerksonCaseClass]("""{"ID":1,"name-thing":"Coda"}""").must(be(NoJerksonCaseClass(1, "Coda"))) + parse[NoJerksonCaseClass]("""{"ID":1,"name-thing":"Coda","meta":[1,"Coda"]}""").must(be(NoJerksonCaseClass(1, "Coda"))) + } + + @Test def `is not parsable from a JSON object with extra fields (using default jackson options)` = { + evaluating { + parse[NoJerksonCaseClass]("""{"ID":1,"name-thing":"Coda","meta":[2,"Not Coda"],"derp":100}""") + }.must(throwA[ParsingException]) + } + + @Test def `is not parsable from an incomplete JSON object` = { + evaluating { + parse[NoJerksonCaseClass]("""{"ID":1}""") + }.must(throwA[ParsingException]) + } + } + + class `A no-jerkson iterator` { + @Test def `generates a JSON object with matching field values` = { + generate(new NoJerksonIterator(3)).must(be("""{"count":3}""")) + } + + @Test def `is parsable from a JSON object with corresponding fields` = { + parse[NoJerksonIterator]("""{"count":3}""").must(beA[NoJerksonIterator]) + parse[NoJerksonIterator]("""{"count":3}""").count.must(be(3)) + } + + @Test def `is not parsable from a JSON object with extra fields (using default jackson options)` = { + evaluating { + parse[NoJerksonIterator]("""{"count":3,"derp":100}""") + }.must(throwA[ParsingException]) + } + + @Test def `is not parsable from an incomplete JSON object` = { + evaluating { + parse[NoJerksonIterator]("""{}""") + }.must(throwA[ParsingException]) + } + } +}