From 0daad19a227ebd145ff47a66e1b305438ab33c73 Mon Sep 17 00:00:00 2001 From: Nathan Date: Fri, 27 Jan 2017 16:59:29 +0100 Subject: [PATCH] Restricted the max number of JsonConversionsProviders to 1. Simplified the conversion from fact to json so that it now also supports lists. No longer required to mess about with a needlessly complicated map. Was unable to achieve the same level of elegance for a solution for lists from json to facts in the given time. This will need to be brute-forced through the map-solution until a more clever way is devised, but it's impeded progress for too long. --- app/controllers/conversion/Conversion.scala | 92 ++++++++++--------- .../conversion/ImplicitConversions.scala | 6 +- app/services/JsonConversionMapsService.scala | 9 +- 3 files changed, 58 insertions(+), 49 deletions(-) diff --git a/app/controllers/conversion/Conversion.scala b/app/controllers/conversion/Conversion.scala index 77d3751..5ca7a35 100644 --- a/app/controllers/conversion/Conversion.scala +++ b/app/controllers/conversion/Conversion.scala @@ -4,59 +4,54 @@ import controllers.conversion.ImplicitConversions._ import org.scalarules.facts.Fact import org.scalarules.finance.nl._ import play.api.data.validation.ValidationError -import play.api.libs.json._ +import play.api.libs.json.{JsObject, _} import scala.reflect.runtime.universe._ trait JsonConversionsProvider { - def contextToJsonConversions: Map[Class[_], (Fact[Any], Any) => JsObject] - def jsonToFactConversions: Map[String, ConvertToFunc] -} - -object DefaultJsonConversion extends JsonConversionsProvider { - override def contextToJsonConversions: Map[Class[_], ConvertBackFunc] = ContextToJsonConversionMap.contextToJsonConversionMap - override def jsonToFactConversions: Map[String, ConvertToFunc] = JsonToFactConversionMap.jsonToFactConversionMap - - object ContextToJsonConversionMap { - val contextToJsonConversionMap: Map[Class[_], ConvertBackFunc] = Map[Class[_], ConvertBackFunc]( - classOf[String] -> { contextStringToJsObject(_, _) }, - classOf[Bedrag] -> { contextBedragToJsObject(_, _) }, - classOf[Percentage] -> { contextPercentageToJsObject(_, _) }, - classOf[BigDecimal] -> { contextBigDecimalToJsObject(_, _) }, - classOf[Boolean] -> { contextBooleanToJsObject(_, _) }, - classOf[java.lang.Boolean] -> { contextBooleanToJsObject(_, _) } - ) - - private def contextStringToJsObject(fact: Fact[Any], factValue: Any): JsObject = factValue match { - case string: String => JsObject(Map(fact.name -> Json.toJson(factValue.toString))) - case _ => throw new IllegalArgumentException - } - - private def contextBedragToJsObject(fact: Fact[Any], factValue: Any): JsObject = factValue match { - case bedrag: Bedrag => JsObject(Map(fact.name -> Json.toJson[Bedrag](bedrag))) - case _ => throw new IllegalArgumentException + private def convertFacts(fact: Fact[Any], factValue: Any): JsObject = JsObject(Map(fact.name -> turnFactsIntoJson(factValue))) + + private def turnFactsIntoJson(factValue: Any): JsValue = //scalastyle:ignore cyclomatic.complexity + try { userSpecifiedConversionsToJson(factValue) } + catch { case e: Exception => factValue match { + case x :: xs => JsArray(for { elem <- (x :: xs)} yield turnFactsIntoJson(elem)) + case bedrag: Bedrag => Json.toJson(bedrag) + case string: String => Json.toJson(string) + case bool: Boolean => JsBoolean(bool) + case bool: java.lang.Boolean => JsBoolean(bool) + case bigDecimal: BigDecimal => Json.toJson(bigDecimal) + case percentage: Percentage => Json.toJson(percentage) + case other: Any => throw new IllegalStateException(s"No legal conversion found for $other, with type ${other.getClass} " + e.fillInStackTrace()) } - - private def contextPercentageToJsObject(fact: Fact[Any], factValue: Any): JsObject = factValue match { - case percentage: Percentage => JsObject(Map(fact.name -> Json.toJson[Percentage](percentage))) - case _ => throw new IllegalArgumentException } - private def contextBigDecimalToJsObject(fact: Fact[Any], factValue: Any): JsObject = factValue match { - case bigDecimal: BigDecimal => JsObject(Map(fact.name -> Json.toJson[BigDecimal](bigDecimal))) - case _ => throw new IllegalArgumentException - } + def contextToJsonConversions(fact: Fact[Any], factValue: Any): JsObject = convertFacts(fact, factValue) + + /** + * override this method in your JsonConversionsProvider to add conversions to Json for types you have implemented, + * without losing all the predefined conversions (custom-specified takes precendence). + * Example: + * override def userSpecifiedConversionsToJson(factValue: Any): JsValue = factValue match { + case thingOfYourType: YourType => Json.toJson[YourType](thingOfYourType) + } + * @param factValue + * @return + */ + def userSpecifiedConversionsToJson(factValue: Any): JsValue = factValue match { + case _ => throw new IllegalStateException("None of the default matches succeeded and no other matches were provided") + } - private def contextBooleanToJsObject(fact: Fact[Any], factValue: Any): JsObject = factValue match { - case bool: Boolean => JsObject(Map(fact.name -> JsBoolean(bool))) - case bool: java.lang.Boolean => JsObject(Map(fact.name -> JsBoolean(bool))) - case _ => throw new IllegalArgumentException - } + def jsonToFactConversions: Map[String, ConvertToFunc] +} - } +object DefaultJsonConversion extends JsonConversionsProvider { + override def jsonToFactConversions: Map[String, ConvertToFunc] = JsonToFactConversionMap.jsonToFactConversionMap object JsonToFactConversionMap { val jsonToFactConversionMap: Map[String, ConvertToFunc] = Map[String, ConvertToFunc]( + weakTypeOf[List[List[List[Bedrag]]]].toString.replace("scala.", "") -> { bedragLijstLijstLijstFunct(_, _) }, + weakTypeOf[List[List[Bedrag]]].toString.replace("scala.", "") -> { bedragLijstLijstFunct(_, _) }, + weakTypeOf[List[Bedrag]].toString.replace("scala.", "") -> { bedragLijstFunct(_, _) }, classOf[String].getTypeName -> { stringFunct(_, _) }, weakTypeOf[String].toString -> { stringFunct(_, _) }, classOf[Bedrag].getTypeName -> { bedragFunct(_, _) }, @@ -77,6 +72,21 @@ object DefaultJsonConversion extends JsonConversionsProvider { case _ => JsError(ValidationError(s"Conversion for BigDecimal fact ${fact.name} failed, corresponding value was not of expected type JsNumber")) } + private def bedragLijstLijstLijstFunct(fact: Fact[Any], factValue: JsValue): JsResult[List[List[List[Bedrag]]]] = factValue match { + case jsNumber: JsArray => Json.fromJson[List[List[List[Bedrag]]]](jsNumber) + case _ => JsError(ValidationError(s"Conversion for Bedrag fact ${fact.name} failed, corresponding value was not of expected type JsNumber")) + } + + private def bedragLijstLijstFunct(fact: Fact[Any], factValue: JsValue): JsResult[List[List[Bedrag]]] = factValue match { + case jsNumber: JsArray => Json.fromJson[List[List[Bedrag]]](jsNumber) + case _ => JsError(ValidationError(s"Conversion for Bedrag fact ${fact.name} failed, corresponding value was not of expected type JsNumber")) + } + + private def bedragLijstFunct(fact: Fact[Any], factValue: JsValue): JsResult[List[Bedrag]] = factValue match { + case jsNumber: JsArray => Json.fromJson[List[Bedrag]](jsNumber) + case _ => JsError(ValidationError(s"Conversion for Bedrag fact ${fact.name} failed, corresponding value was not of expected type JsNumber")) + } + private def bedragFunct(fact: Fact[Any], factValue: JsValue): JsResult[Bedrag] = factValue match { case jsNumber: JsNumber => Json.fromJson[Bedrag](jsNumber) case _ => JsError(ValidationError(s"Conversion for Bedrag fact ${fact.name} failed, corresponding value was not of expected type JsNumber")) diff --git a/app/controllers/conversion/ImplicitConversions.scala b/app/controllers/conversion/ImplicitConversions.scala index b3c4366..b65b075 100644 --- a/app/controllers/conversion/ImplicitConversions.scala +++ b/app/controllers/conversion/ImplicitConversions.scala @@ -21,11 +21,7 @@ object ImplicitConversions { */ def writes(context: Context, conversionMap: JsonConversionsProvider): JsObject = { context.map{ case (fact:Fact[Any], factValue: Any) => - conversionMap.contextToJsonConversions.get(factValue.getClass) match { - case function: Some[ConvertBackFunc] => function.get(fact, factValue) - case None => throw new IllegalStateException(s"Unable to find suitable toJson conversion for Fact with name ${fact.name} with " + - s"valuetype ${factValue.getClass.getTypeName} in factConversionMap") - } + conversionMap.contextToJsonConversions(fact, factValue) }.reduceLeft(_ ++ _) } } diff --git a/app/services/JsonConversionMapsService.scala b/app/services/JsonConversionMapsService.scala index 71e58c3..b275e01 100644 --- a/app/services/JsonConversionMapsService.scala +++ b/app/services/JsonConversionMapsService.scala @@ -4,6 +4,7 @@ import javax.inject.{Inject, Singleton} import controllers.conversion._ import play.api.Configuration +import play.api.libs.json.JsValue @Singleton class JsonConversionMapsService @Inject()(configuration: Configuration, jarLoaderService: JarLoaderService) { @@ -17,9 +18,11 @@ class JsonConversionMapsService @Inject()(configuration: Configuration, jarLoade case (_, map: JsonConversionsProvider) => map.jsonToFactConversions } - override def contextToJsonConversions: Map[Class[_], ConvertBackFunc] = DefaultJsonConversion.contextToJsonConversions ++ jsonConversionMaps.flatMap{ - case (_, map: JsonConversionsProvider) => map.contextToJsonConversions - } + override def userSpecifiedConversionsToJson(factValue: Any): JsValue = + if (jsonConversionMaps.isEmpty) { DefaultJsonConversion.userSpecifiedConversionsToJson(factValue) } + else if (jsonConversionMaps.size > 1) throw new IllegalStateException("Only a single instance of JsonConversionsProvider may be provided!") + else { jsonConversionMaps.toList.head._2.userSpecifiedConversionsToJson(factValue) } + } }