scalagenericsshapelesscoproduct

How to make only few datatype which is not related to each other acceptable by generics


There is a trait which works perfectly. However, I would like to refactor the part related to generic [T] in order to limit the data type which could be accepted by generic [T] (I need only Option[JsValue] , JsValue , StringEnumEntry , String ). Is it possible to solve this problem through shapeless coproduct? Maybe there are other solutions?

trait ParameterBinders extends Log {

  def jsonBinder[T](json: T, jsonType: java.lang.String = "json"): ParameterBinderWithValue = {
    val jsonObject = new PGobject()
    jsonObject.setType(jsonType)
    json match {
      case json: Option[JsValue] =>
        jsonObject.setValue(json.map(Json.stringify).orNull)
      case json: JsValue =>
        jsonObject.setValue(Json.stringify(json))
      case json: StringEnumEntry =>
        jsonObject.setValue(json.value)
      case json: String =>
        jsonObject.setValue(json)
      case _ =>
        logger.error("unexpected data type ")
    }
    if (jsonType == "JSONSCHEMATYPE" || jsonType == "SYSPROPERTYTYPE") {
      ParameterBinder(this, (ps, i) => {
        ps.setObject(i, jsonObject)
      })
    } else {
      ParameterBinder(json, (ps, i) => {
        ps.setObject(i, jsonObject)
      })
    }

  }
}

Solution

  • The easiest way is to use an ADT as described in the link of the first comment. If you don't want to change the types that are accepted in jsonBinder then you can solve the problem by using a typeclass.

    e.g.

    trait JsonBindValue[T] {
        def value(t: T): String
    }
    

    you would then have to provide instances for your accepted datatypes

    object JsonBindValue {
        implicit val OptJsBinder = new JsonBindValue[Option[JsValue]] {
            def value(t: Option[JsValue]): String = {
                t.map(Json.stringify).orNull
            }
        }
       ... more instances here
    }
    

    finally your function would look like this:

    def jsonBinder[T : JsonBindValue](json: T, jsonType: java.lang.String = "json"): ParameterBinderWithValue = {
        val binder = implicitly[JsonBindValue[T]]
        jsonObject.setType(jsonType)
        jsonObject.setValue(binder.value(json))
        ...
    }
    

    if you call the function without a implicit instance in scope you will get a compile time error.