Home > database >  Changing implicit value in scala response of nested object
Changing implicit value in scala response of nested object

Time:10-19

I have a controller

def getCars(notation: Option[Boolean] = Some(false)) = identified.auth(hasOceanScope).async { implicit request =>
  carService.getCars().map {
    case Seq() => Response.NotFound
    case cars => Response.Ok(cars)
  }
}

Car case class looks like this:

case class Car(
  name: String,
  createdAt: LocalDateTimeOffset,
  wheels: Seq[Wheel]
)

object Car{
  implicit val wheelFormat = Wheel.format
  implicit def toOffset(date: LocalDateTime): LocalDateTimeOffset = LocalDateTimeOffset.apply(date)

  implicit val format = Json.format[Car]



case class Wheel(
  name: String,
  createdAt: LocalDateTimeOffset
)

object Wheel{
  implicit val format = Json.format[Wheel]
  implicit def toOffset(date: LocalDateTime): LocalDateTimeWithOffset = LocalDateTimeWithOffset.apply(date)
 )

When notation query parameter is true -> want to return createdAt Car object and Wheel object field with notation => 2022-10-22T00:00:00#1 When notation query parameter is false -> want to return createdAt Car object and Wheel object field without notation => 2022-10-22T00:00:00

That is why I have create two formats in LocalDateTimeOffset object

case class LocalDateTimeWithOffset(dt: LocalDateTime, offset: Int) {

  val localDateTimeWithOffsetReads: Reads[LocalDateTimeWithOffset] = Reads.of[String].flatMap {
    str => if (str.contains("#")) {
      val (dt, offset) = str.splitAt(str.indexOf("#"))
      Reads.pure(LocalDateTimeWithOffset(LocalDateTime.parse(dt), offset.drop(1).toInt))
    } else {
      Reads.pure(LocalDateTimeWithOffset(LocalDateTime.parse(str), 1))
    }
  }

  val localDateTimeWithOffsetWrites: Writes[LocalDateTimeWithOffset] = new Writes[LocalDateTimeWithOffset] {
    override def writes(a: LocalDateTimeWithOffset): JsValue = JsString(a.dt.format(dateTimeUTCFormatter)   s"#${a.offset}")
  }

  val localDateTimeWithOffsetWritesOff: Writes[LocalDateTimeWithOffset] = new Writes[LocalDateTimeWithOffset] {
    override def writes(a: LocalDateTimeWithOffset): JsValue = JsString(a.dt.format(dateTimeUTCFormatter))
  }

  val localDateTimeWithoutOffsetFormat: Format[LocalDateTimeWithOffset] = Format(localDateTimeWithOffsetReads, localDateTimeWithOffsetWritesOff)
  val localDateTimeWithOffsetFormat: Format[LocalDateTimeWithOffset] = Format(localDateTimeWithOffsetReads, localDateTimeWithOffsetWrites)

  implicit var format: Format[LocalDateTimeWithOffset] = localDateTimeWithoutOffsetFormat
}

But how can I use two different formats from controller based on notation query parameter value?

CodePudding user response:

Well just looking at your question's title, changing implicit value is not something you would see Scala developers do, because compiler is responsible to lookup for implicit values, and you would definitely want to avoid ambiguous implicits found error. instead, you see developers using something so called type class instance constructor or something similar. This is how it works in your case:

Assuming you have a class A, which can be formatted to/from Json in many ways:

case class A(field1: String) // could have more fields

object A {
  val formatFirstApproach: Format[A] = ???
  val formatSecondApproach: Format[A] = ???
  // note that the above instances are not implicit
  def getFormat(somePredicate: Boolean): Format[A] = {
  // input parameters can be anything, these are the parameters you need,
  // in order to be able to decide which instance to return
  if (somePredicate) formatFirstApproach else formatSecondApproach
  }
}

And then, given a class B which has an instance variable of type A in it, you can use the type class instance constructor:

case class B(a: A, field2: Int)

object B {
  // this is the type class instance constructor, since it constructs an instance of a type class (Format in this case)
  implicit def format(implicit aFormatter: Format[A]): Format[B] = Json.format
}

And the thing is, you probably would not care about the serialization unless in the controller layer, so in the controller layer you can do:

def someApi(flag: Boolean) = Action async { req => 
  implicit val aFormatter = A.getFormat(flag) // that's it, you don't need to mention anything more anywhere
  businessLogic().map {
    case Seq() => Response.NotFound
    case cars => Response.Ok(Json.toJson(cars))
  }
}
  • Related