Search code examples
scalaplayframework-2.0play-json

Play JsPath writes for Infinite Double


I am trying to implement a function writing Doubles that might have a value of Infinity (which does not exist in JSON).

Here are some examples of what I am trying to achieve:

Input: Double.PositiveInfinity
Output:
{
  "positiveInfinty": true,
  "negativeInfinty": false,
  "value": null
}

Input: 12.3
Output:
{
  "positiveInfinty": false,
  "negativeInfinty": false,
  "value": 12.3
}

So far, I have created an enhanced JsPath class, and added my function named writeInfinite:

case class EnhancedJsPath(jsPath: JsPath) {

    private def infinityObject(pos: Boolean, neg: Boolean, value: Option[Double]): JsObject = Json.obj(
        "positiveInfinity" -> pos,
        "negativeInfinity" -> neg,
        "value" -> value
    )

    def writeInfinite: OWrites[Double] = OWrites[Double] { d =>
        if (d == Double.PositiveInfinity) { infinityObject(true, false, None) }
        else if (d == Double.NegativeInfinity) { infinityObject(false, true, None) }
        else { infinityObject(false, false, Some(d)) }
    }
}

object EnhancedJsPath {
    implicit def jsPathToEnhancedJsPath(jsPath: JsPath): EnhancedJsPath = EnhancedJsPath(jsPath)
}

The code all compiles, and here is the test I am using :

case class Dummy(id: String, value: Double)
object Dummy {
    implicit val writes: Writes[Dummy] = (
        (JsPath \ "id").write[String] and
        (JsPath \ "value").writeInfinite
    )(unlift(Dummy.unapply))
}

test("EnhancedJsPath.writesInfinite should set positive infinity property") {

    val d = Dummy("123", Double.PositiveInfinity, Some(Double.PositiveInfinity))
    val result = Json.toJson(d)
    val expected = Json.obj(
      "id" -> "123",
      "value" -> Json.obj(
        "positiveInfinity" -> true,
        "negativeInfinity" -> false,
        "value" -> JsNull
      )
    )

    assert(result == expected)
}

The test is failing because the value of result is:

{
    "id": "123",
    "positiveInfinity": true,
    "negativeInfinity": false,
    "value": null
}

Instead of:

{
    "id": "123",
    "value": {
        "positiveInfinity": true,
        "negativeInfinity": false,
        "value": null
    }
}

I can't figure out how to modify my writeInfinite to respect the path.


Solution

  • Taking a look at JsPath#write[A] or JsPath#writeNullable[A] helps:

    def write[T](implicit w: Writes[T]): OWrites[T] = Writes.at[T](this)(w)
    

    Writes.at accepts a JsPath, which is how write and writeNullable preserves the path they are called on. All you need to do is wrap your current implementation of writeInfinite with it, passing the jsPath value along:

    def writeInfinite: OWrites[Double] = Writes.at(jsPath)(OWrites[Double] { d =>
        if (d == Double.PositiveInfinity) infinityObject(true, false, None)
        else if (d == Double.NegativeInfinity) infinityObject(false, true, None)
        else infinityObject(false, false, Some(d))
    })
    

    And it works:

    scala> Json.toJson(d)
    res5: play.api.libs.json.JsValue = {"id":"123","value":{"positiveInfinity":true,"negativeInfinity":false,"value":null}}