Tomer
Tomer

Reputation: 2448

play - try to implements Writes to List[Any]

I have a case class that looks like this:

case class A(name: String, values: List[Any]) While values can be a list of long or string.

I am trying to implement a Writes without success. This the code I have:

implicit val myWrites: Writes[A] = (
(JsPath \ "name").write[String] and
  (JsPath \ "values").write[JsArray].contramap[List[Any]](
    (list: List[Any]) => list match{
    case longs: List[Long] => JsArray(longs.map(l => JsNumber(l)))
    case strings: List[String] => JsArray(strings.map(s => JsString(s)))
  })
) (unlift(A.unapply))

From some reason, when I am trying to write the case class with the following values:

A("name", List("val1", "val2"))

I get the following exception:

java.lang.String cannot be cast to java.lang.Long
java.lang.ClassCastException: java.lang.String cannot be cast to                             
java.lang.Long
at scala.runtime.BoxesRunTime.unboxToLong(BoxesRunTime.java:105)

It fails on the first case line : case longs: List[Long]

I am not sure what I am doing wrong. any idea?

Thanks!

Upvotes: 2

Views: 88

Answers (2)

Tomer
Tomer

Reputation: 2448

Another suggestion to the solution:

implicit val myWrites: Writes[A] = (
(JsPath \ "name").write[String] and
  (JsPath \ "values").write[JsArray].contramap[List[Any]](
    case l if l.isEmpty => JsArray(Seq())
    case list@List(_: Long, _*) => JsArray(list.map(l => JsNumber(l.asInstanceOf[Long])))
    case list@List(_: String, _*) => JsArray(list.map(s => JsString(s.asInstanceOf[String])))
) (unlift(A.unapply))

Upvotes: 0

Mikesname
Mikesname

Reputation: 8901

You're pretty close, but remember that when you say List[Any] it means the list values can be heterogeneous as far as the type system is concerned, and as @m-z points out, the generic types will be erased.

In this situation I'd transform the List[Any] into a List[JsValue] using collect to discard anything that's not either a string or a long:

case class A(name: String, values: List[Any])

implicit val myWrites: Writes[A] = (
  (JsPath \ "name").write[String] and
  (JsPath \ "values").write[List[JsValue]].contramap[List[Any]](
    _.collect {
      case str: String => JsString(str)
      case long: Long => JsNumber(long)
    })
)(unlift(A.unapply))

I've had to do something similar myself recently for dealing with a web service that accepted heterogeneous JSON arrays, but it's usually a good idea to avoid using Any at the Scala level.

Upvotes: 2

Related Questions