acjay
acjay

Reputation: 36671

Refering to self type of a trait

I wrote a trait to mix into a class the ability to serialize itself to query string parameters, leveraging an existing JSON Writes instance. In order to use that Writes instance as a parameter, I need to know the type into which this trait is being mixed. I'm getting that using a type parameter (which should be the class itself) and a self-type annotation. I'm wondering if there's a DRYer way of doing this, which doesn't require the type parameter?

Here's my code:

trait ConvertibleToQueryString[T] {
  this: T =>

  /** Transformation of field names in obj to query string keys */
  def objToQueryStringMapping: Map[JsPath, JsPath] = Map.empty

  /**
   * Convert a model to a Map, for serialization to a query string, to be used
   * in a REST API call.
   * @param writes writer for `obj`
   * @return
   */
  def toQueryStringMap(implicit writes: Writes[T]): Map[String, String] = {
    // Get a map of key -> JsValue from obj
    val mapObj = Json.toJson(this).transform(moveKeys(objToQueryStringMapping)).get.value

    // Convert the JsValue values of the map to query strings
    mapObj.mapValues(jsValueToQueryStringValue).filter(_._2.nonEmpty).toMap
  }
}

, to be used as such:

case class MyClass(param1: String, param2: Int) extends ConvertibleToQueryString[MyClass]

, that final type parameter being the thing that's annoying me. It's fully unconstrained, but it should really just be "the type of the class I get mixed into". Is there a way to express this?

Upvotes: 2

Views: 116

Answers (1)

wingedsubmariner
wingedsubmariner

Reputation: 13667

Why not use the pimp-encrich-my-library pattern:

implicit class ConvertibleToQueryString[T: Writes](x: T) {
  def objToQueryStringMapping: Map[JsPath, JsPath] = Map.empty

  def toQueryStringMap: Map[String, String] = {
    // Get a map of key -> JsValue from obj
    val mapObj = Json.toJson(x).transform(moveKeys(objToQueryStringMapping)).get.value

    // Convert the JsValue values of the map to query strings
    mapObj.mapValues(jsValueToQueryStringValue).filter(_._2.nonEmpty).toMap
  }
}

Now you don't need the extends ... at all on the classes you want to serialize.

Upvotes: 1

Related Questions