9

I have a value class that accepts an Either, which I would like to generate a Play for Scala v2.5.6 JSON Format for:

import org.joda.time.{DateTime, Duration}

case class When(when: Either[DateTime, Duration]) extends AnyVal

I think I have the writes method figured out; the problems I am having are with the reads method. I've attempted two approaches, both failed for different reasons.

Attempt #1, showing both the reads and writes methods:

import play.api.libs.json._
import play.api.libs.json.Json.obj

object When {    
  def apply(dateTime: DateTime): When = When(Left(dateTime))

  def apply(duration: Duration): When = When(Right(duration))

  implicit val whenFormat = new Format[When] {
    def reads(json: JsValue): JsResult[When] = {
      val reads = (__ \ "dateTime").read[Long] { (millis: Long) =>
        When(Left(new DateTime(millis)))
      } | (__ \ "duration").read[Long] { (millis: Long) =>
        When(Right(new Duration(millis)))
      }
      reads.reads(json)
    }

    def writes(o: When): JsValue = obj(
      o.when.fold(
        duration => "duration" -> duration.getMillis,
        dateTime => "dateTime" -> dateTime.getMillis
      )
    )
  }
}

The error messages are:

overloaded method value read with alternatives:
[error]   (t: Long)play.api.libs.json.Reads[Long] <and>
[error]   (implicit r: play.api.libs.json.Reads[Long])play.api.libs.json.Reads[Long]
[error]  cannot be applied to (Long => When)
[error]       val reads = (__ \ "dateTime").read[Long] { (millis: Long) =>

[error] overloaded method value read with alternatives:
[error]   (t: Long)play.api.libs.json.Reads[Long] <and>
[error]   (implicit r: play.api.libs.json.Reads[Long])play.api.libs.json.Reads[Long]
[error]  cannot be applied to (Long => When)
[error]       } | (__ \ "duration").read[Long] { (millis: Long) =>

Attempt #2, just showing the reads method:

def reads(json: JsValue): JsResult[When] =
  JsSuccess(
    When(Left(new DateTime((__ \ "dateTime").read[Long]))) ||
    When(Right(new Duration((__ \ "duration").read[Long])))
  )

The error message is:

value || is not a member of When
[error]  Note: implicit value whenFormat is not applicable here because it comes after the application point and it lacks an explicit result type
[error] Error occurred in an application involving default arguments.

I'd just like something that works, and I don't care what approach is used (even one I did not show), so long as it is maintainable and efficient. It would also be helpful to know what was wrong with each of these approaches.

2
  • try dropping extends AnyVal from When
    – Łukasz
    Commented Sep 8, 2016 at 6:09
  • Removing extends AnyVal made no difference to either approach
    – Mike Slinn
    Commented Sep 8, 2016 at 14:33

1 Answer 1

10

Here is working example of how to do this:

import org.joda.time.{DateTime, Duration}
import play.api.libs.functional.syntax._
import play.api.libs.json.Reads._
import play.api.libs.json._

object When {
  def apply(dateTime: DateTime): When = When(Left(dateTime))

  def apply(duration: Duration): When = When(Right(duration))

  val reads: Reads[When] = 
    (__ \ "dateTime").read[Long].map(millis => When(Left(new DateTime(millis)))) |
    (__ \ "duration").read[Long].map(millis => When(Right(new Duration(millis))))

  val writes: Writes[When] = new Writes[When] {
    override def writes(o: When): JsValue = Json.obj(
      o.when.fold(
        duration => "duration" -> duration.getMillis,
        dateTime => "dateTime" -> dateTime.getMillis
      )
    )
  }

  implicit val format = Format(reads, writes)
}

basically you should map the reads

(__ \ "dateTime").read[Long]

gives you Reads[Long], then you can map result to When. You were just passing parameter. This parameter could be a Long, to just ignore what is read and return that value, or implicit reads for long that you probably don't want to change and should let it stay implicit.

So then in similar way you can create another reads for duration and combine them with alternative (|) and done, you have reads.

Your second approach makes no sense. Either use reads and compose them or just manually check if something is there and if not return a different result, but it is not worth doing this, just go with default approach.

4
  • Perfect! I marked your answer as the accepted solution. BTW, the first line of code you show is improperly formatted.
    – Mike Slinn
    Commented Sep 8, 2016 at 16:47
  • Oops, I accepted too soon. The | operator is undefined.
    – Mike Slinn
    Commented Sep 8, 2016 at 16:51
  • You need to import syntax, see update. I fixed formatting as well, thanks.
    – Łukasz
    Commented Sep 8, 2016 at 16:59
  • Thankyou for providing imports! It really helps when answers include a complete working example:)
    – TonyC
    Commented Jun 23, 2017 at 2:07

Not the answer you're looking for? Browse other questions tagged or ask your own question.