2

I have a Scala case class

case class NumericParam(minValue: Option[Int] = None,
                        maxValue: Option[Int] = None,
                        decimalPlaces: Option[Int] = None,
                        signed: Option[Boolean] = None,
                        hintText: Option[String] = None)

and its companion object, where I defined an implicit writes method

object NumericParam {
    implicit val writes = new Writes[NumericParam] {
        override def writes(value: NumericParam): JsValue = {
          Json.obj(
            "dataType" -> "Numeric",
            "minValue" -> value.maxValue,
            "maxValue" -> value.maxValue,
            "decimalPlaces" -> value.decimalPlaces,
            "signed" -> value.signed,
            "hintText" -> value.hintText
          )
        }
    }
}

I am adding the field dataType. Is there any way to use the macro-derived Writes value (from Json.writes[NumericParam]) and just add the additional dataType field?

1 Answer 1

7

You can use Writes#transform to do it. One way would be to use a function JsValue => JsValue. The safe way:

implicit val writes = Json.writes[NumericParam] transform { js =>
  js match {
    case obj: JsObject => obj + ("dataType" -> JsString("Numeric"))
    case _ => js
  }
}

However, we really know that js should always be a JsObject, since we're operating on a specific type, so we can shorten it.

implicit val writes = Json.writes[NumericParam]
   .transform(_.as[JsObject] + ("dataType" -> JsString("Numeric")))

Example:

scala> val num = NumericParam(Some(1), Some(10), Some(2), Some(false), Some("test"))
num: NumericParam = NumericParam(Some(1),Some(10),Some(2),Some(false),Some(test))

scala> Json.toJson(num)
res5: play.api.libs.json.JsValue = {"minValue":1,"maxValue":10,"decimalPlaces":2,"signed":false,"hintText":"test","dataType":"Numeric"}

To make the above more type-safe and generic, we can use some implicit magic to extend OWrites (which always writes to JsObject).

implicit class OWritesExt[A](owrites: OWrites[A]) {

  /** Add a (key, value) pair to the JSON object,
   *  where the value is constant.
   */
  def withConstant[B : Writes](key: String, value: B): OWrites[A] = 
    withValue(key, _ => value)

  /** Add a key, value pair to the JSON object that is written, where
   *  the value depends on the original object.
   */
  def withValue[B : Writes](key: String, value: A => B): OWrites[A] = 
    new OWrites[A] {
      def writes(a: A): JsObject = owrites.writes(a) ++ Json.obj(key -> value(a))
    }

}

Usage:

implicit val writes = Json.writes[NumericParam]
    .withValue("type", _.getClass.toString)
    .withConstant("other", "something")

scala> Json.toJson(NumericParam(Some(1)))
res6: play.api.libs.json.JsValue = {"minValue":1,"type":"class NumericParam","other":"something"}

Now you can scrap some of the original boilerplate, and chain calls like this together. Now I'm just wondering why I haven't been doing this all along.

Sign up to request clarification or add additional context in comments.

1 Comment

That is what I was looking for. I thought it would involve transform, but could not figure out how. Thanks.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.