diff --git a/json/json-derivation/src/main/java/io/sphere/json/annotations/JSONTypeHintField.java b/json/json-derivation/src/main/java/io/sphere/json/annotations/JSONTypeHintField.java index 2af2f215..8fcd7797 100644 --- a/json/json-derivation/src/main/java/io/sphere/json/annotations/JSONTypeHintField.java +++ b/json/json-derivation/src/main/java/io/sphere/json/annotations/JSONTypeHintField.java @@ -13,4 +13,5 @@ @Target({TYPE}) public @interface JSONTypeHintField { String value() default "type"; -} \ No newline at end of file + String defaultType() default ""; +} diff --git a/json/json-derivation/src/main/scala/io/sphere/json/generic/package.fmpp.scala b/json/json-derivation/src/main/scala/io/sphere/json/generic/package.fmpp.scala index d50e7afc..9980f7e8 100644 --- a/json/json-derivation/src/main/scala/io/sphere/json/generic/package.fmpp.scala +++ b/json/json-derivation/src/main/scala/io/sphere/json/generic/package.fmpp.scala @@ -373,6 +373,7 @@ package object generic extends Logging { val fieldWithJSONTypeHint = clazz.getAnnotation(classOf[JSONTypeHintField]) val typeField = if (fieldWithJSONTypeHint != null) fieldWithJSONTypeHint.value() else defaultTypeFieldName + val defaultType = if (fieldWithJSONTypeHint != null) fieldWithJSONTypeHint.defaultType() else "" new FromJSON[T] with TypeSelectorFromJSONContainer { override def typeSelectors: List[TypeSelectorFromJSON[_]] = allSelectors @@ -384,6 +385,10 @@ package object generic extends Logging { case Some(ts) => ts.read(o).asInstanceOf[ValidatedNel[JSONError, T]] case None => jsonParseError("Invalid type value '" + t + "' in '%s'".format(compactJson(o))) } + case None if defaultType.nonEmpty => readMap.get(defaultType) match { + case Some(ts) => ts.read(o).asInstanceOf[ValidatedNel[JSONError, T]] + case None => jsonParseError("Invalid default type value '" + defaultType + "' in '%s'".format(compactJson(o))) + } case None => jsonParseError("Missing type field '" + typeField + "' in '%s'".format(compactJson(o))) } case _ => jsonParseError("JSON object expected.") diff --git a/json/json-derivation/src/test/scala/io/sphere/json/generic/JsonTypeHintDefaultsSpec.scala b/json/json-derivation/src/test/scala/io/sphere/json/generic/JsonTypeHintDefaultsSpec.scala new file mode 100644 index 00000000..5fca71d0 --- /dev/null +++ b/json/json-derivation/src/test/scala/io/sphere/json/generic/JsonTypeHintDefaultsSpec.scala @@ -0,0 +1,64 @@ +package io.sphere.json.generic + +import cats.data.Validated.Valid +import io.sphere.json._ +import io.sphere.json.generic.JsonTypeHintDefaultsSpec._ +import org.json4s._ +import org.scalatest.matchers.must.Matchers +import org.scalatest.wordspec.AnyWordSpec + +class JsonTypeHintDefaultsSpec extends AnyWordSpec with Matchers { + + "JSONTypeHintField default value" must { + + "fallback to default implementation when discriminator field is missing" in { + val json = """{ "addressKey": "address" }""" + getFromJSON[WithDefaultTypeField](json) must be(MissingHint("address")) + getFromJSON[WithOverridenHintField](json) must be(MissingHint2("address")) + } + + "test only for default implementation when discriminator field is missing" in { + val json = """{ "shippingKey": "wrong field" }""" + a[JSONException] must be thrownBy getFromJSON[WithDefaultTypeField](json) + a[JSONException] must be thrownBy getFromJSON[WithOverridenHintField](json) + } + + "ignore default type when discriminator is provided" in { + getFromJSON[WithDefaultTypeField]( + """ + { + "type": "MissingHint", + "addressKey": "address" + } + """.stripMargin) must be(MissingHint("address")) + getFromJSON[WithOverridenHintField]( + """ + { + "version": "old", + "addressKey": "address" + } + """.stripMargin) must be(MissingHint2("address")) + } + } +} + +object JsonTypeHintDefaultsSpec { + @JSONTypeHintField(value = "type", defaultType = "MissingHint") + sealed trait WithDefaultTypeField + case class MissingHint(addressKey: String) extends WithDefaultTypeField + case class WithHint(shippingKey: String) extends WithDefaultTypeField + object WithDefaultTypeField { + implicit val json: JSON[WithDefaultTypeField] = deriveJSON[WithDefaultTypeField] + } + + + @JSONTypeHintField(value = "version", defaultType = "old") + sealed trait WithOverridenHintField + @JSONTypeHint("old") + case class MissingHint2(addressKey: String) extends WithOverridenHintField + @JSONTypeHint("new") + case class WithHint2(shippingKey: String) extends WithOverridenHintField + object WithOverridenHintField { + implicit val json: JSON[WithOverridenHintField] = deriveJSON[WithOverridenHintField] + } +}