Question

I have a json object:

{"fieldA": "valueForA", "fieldB":"valueForB", "fieldC":"valueForC"}

I want to map it to my case class:

case class BetterNamed(gloriousAlpha:String, repugnantBeta:String, ingratiousCharlie:String) {}

But I don't want to write all of:

val read:Reads[BetterNamed] = (
  (__ \ "fieldA").read[String] and
  (__ \ "fieldB").read[String] and
  (__ \ "fieldC").read[String]
)(BetterNamed.apply _)

because I'm repeating myself WAY too much.

What I want to write is:

val read:Reads[BetterNamed] = (Seq(
  "fieldA",
  "fieldB",
  "fieldC").map( (__ \ _).read[String] ).reduce(_ and _)(BetterNamed.apply _)

There are numerous applications in my system for that form of field access, however the code as written above fails to compile telling me that BetterNamed does not accept parameters.

Can anyone tell me how to correct the above code to compile?
My first guess is that it needs a type hint somewhere...

If it's not possible I would greatly appreciate an explanation of why it's impossible

Was it helpful?

Solution

Why it doesn't work?

The reduce method of type Seq has a following declaration:

reduce[A1 >: A](op: (A1, A1) => A1): A1

That means reducing function op needs to have a type

(A1, A1) => A1)

For some A1. Now let us look at the types of intermediate results of the reduction we wanted to perform (x is a value of type Reads[String])

x and x : FunctionalBuilder[Reads]#CanBuild2[String,String]
x and x and x : FunctionalBuilder[Reads]#CanBuild3[String,String,String]

You can find these types defined here: http://www.playframework.com/documentation/api/2.1.x/scala/index.html#play.api.libs.functional.FunctionalBuilder

The fact is, that and method does not have a nice polymorphic type reduce method needs, so we need to find ourselves some other method to solve our problem.

This problem is a very similar analogue to a problem of processing a tuple instead of a list - there is no very easy way to write a function that handles arbitrary length tuples generically. But still, there is a lot of things we can do.

Using implicits

One way to solve this problem is by using specialized implicit declaration for every size of tuple. If you don't have to handle a lot of different cases, it is a nice and clean idiomatic scala solution:

trait Reducer[T] {
  def reduce(t : T) : Reads[T]
}

object TupleReducer {
  implicit val reducer1 = new Reducer[String] {
    def reduce(s : String) = (__ \ s).read[String]
  }

  implicit val reducer2 = new Reducer[(String, String)] {
    def reduce(s : (String, String)) = (
        (__ \ s._1).read[String] and
        (__ \ s._2).read[String]
      ).tupled
    }

    implicit val reducer3 = new Reducer[(String, String, String)] {
        def reduce(s : (String, String, String)) = (
            (__ \ s._1).read[String] and 
            (__ \ s._2).read[String] and 
            (__ \ s._3).read[String]
          ).tupled
    }

    def make[T, Res](t : T, f : T => Res)(implicit reducer : Reducer[T]) = {           
      reducer.reduce(t).map(f)
    }
}

In the above code we create a different reducer for different tuple size, inject it as an implicit argument to our make function and as a last step use map to make Reads[(String,String,String)] a Reads[BetterNamed].

Here is a usage example:

import TupleReducer._
implicit val read : Reads[BetterNamed] = TupleReducer.make(
  ("fieldA", "fieldB", "fieldC"),   
  BetterNamed.apply _ tupled
)

Trickier way

But what if we really want the freedom of Lists (or other sequence types, I justed used lists as an example)? Idea how to do that is taken from this stackoverflow thread: Is there way to create tuple from list(without codegeneration)? from which I have borrowed method for creating tuple from list in a generic way. Armed with that gear we can write ListReducer:

object ListReducer {
  def map(xs : List[String]) : List[Reads[String]] = xs.map(x => (__ \ x).read[String])

  def reduce(xs : List[Reads[String]]) : Reads[List[String]] = xs match {
    case Nil => Reads[List[String]] { _ => JsSuccess(Nil) }
    case (head::rest) => for {
      h <- head
      r <- reduce(rest)
    } yield(h::r)
  }

  def toTuple[A <: Object,Product](as:List[A]) : Product = {
    val tupleClass = Class.forName("scala.Tuple" + as.size)
    tupleClass.getConstructors.apply(0).newInstance(as:_*).asInstanceOf[Product]
  }

  def createTransformer[X,Y](f : X => Y)(xs : List[String]) : Y = {
    f(toTuple[String, X](xs))
  }

  def make[X,Y](xs : List[String], fn : X => Y) : Reads[Y] = {   
    reduce(map(xs)).map(createTransformer[X,Y](fn))
  }
}

This code divides the problem into several steps:

  1. Firstly, it creates List[Reads[String]] from the list of string, extracting each field separately in the method map
  2. Next, it creates Reads[List[String]] from List[Reads[String]] applying each of the reads separately and merging results.
  3. It uses toTuple method to pack the List[String] into the corresponding tuple typle, that can be fed into BetterNamed.apply function that constructs BetterNamed object.

Here is a usage example:

implicit val read = ListReducer.make(
  List("fieldA", "fieldB", "fieldC"), 
  BetterNamed.apply _ tupled
)

I hope that helps.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top