[2.1] Json.format macros and Tuple2

1,073 views
Skip to first unread message

Alex Jarvis

unread,
Jan 21, 2013, 6:54:05 PM1/21/13
to play-fr...@googlegroups.com
Hello,

Does the Json macros stuff allow for Tuple types yet? My case class has an attribute with a Tuple type that doesn't seem to compile.. e.g. (Double, Double)

I just spent some time writing the format combinator manually (such fun) and it all compiles now and so should hopefully work this way, but it would be nice to know if the macros feature should be working and if I'm doing something wrong perhaps?

Thanks in advance!
Alex

James Roper

unread,
Jan 21, 2013, 8:11:56 PM1/21/13
to play-framework
I don't think we have built in support for it yet, the main reason being that JSON doesn't support tuples.  Though, a tuple can be expressed as an array.

What does your format combinator look like?  Here's what a reads might look like:

def tuple2Reads[A, B](implicit aReads: Reads[A], bReads: Reads[B]): Reads[Tuple2[A, B]] = Reads[Tuple2[A, B]] {
  case JsArray(arr) if arr.value.size == 2 => for {
    a <- aReads.reads(arr.value(0))
    b <- bReads.reads(arr.value(1))
  } yield (a, b)
  case _ => JsError(Seq(JsPath() -> Seq(ValidationError("Expected array of two elements"))))
}


Alex

--
 
 



--
James Roper
Software Engineer

Typesafe - The software stack for applications that scale
Twitter: @jroper

Pascal Voitot Dev

unread,
Jan 22, 2013, 2:55:58 AM1/22/13
to play-fr...@googlegroups.com
Exactly.
The Macro only works for case classes for now.

Pascal


--
 
 

Alex Jarvis

unread,
Jan 22, 2013, 7:02:50 AM1/22/13
to play-fr...@googlegroups.com
Hi James,

Thanks a lot for this example - you really hit the nail on the head with the validation error of no more than 2 elements! I'm actually expressing a lat/lon pair in a json array to save space during transport and so if I was using a List[Double] would have to validate that a untrusted client wasn't sending more than 2 elements..

I've amended the example so it compiles and also used it (along with the source in Writes) to write a tuple2Writes as well, allowing me to either write a Json macros format (single line) or manually writing the format combinator, allowing me to reduce the field name size for transport also :)

implicit def tuple2Reads[A, B](implicit aReads: Reads[A], bReads: Reads[B]): Reads[Tuple2[A, B]] = Reads[Tuple2[A, B]] {
  case JsArray(arr) if arr.size == 2 => for {
    a <- aReads.reads(arr(0))
    b <- bReads.reads(arr(1))

  } yield (a, b)
  case _ => JsError(Seq(JsPath() -> Seq(ValidationError("Expected array of two elements"))))
}

implicit def tuple2Writes[A, B](implicit aWrites: Writes[A], bWrites: Writes[B]): Writes[Tuple2[A, B]] = new Writes[Tuple2[A, B]] {
  def writes(tuple: Tuple2[A, B]) = JsArray(Seq(aWrites.writes(tuple._1), bWrites.writes(tuple._2)))
}

Please let me know if you see anything wrong with this code.

Cheers!
Alex

--
 
 

Pascal Voitot Dev

unread,
Jan 23, 2013, 5:20:13 AM1/23/13
to play-fr...@googlegroups.com
Hi guys!

Just remembered (or discovered a feature depending on the way you see it ;) ) that you can write great tupled Reads using Json combinators

scala> (JsPath(0).read[String] and JsPath(1).read[Int]).tupled
res4: play.api.libs.json.Reads[(String, Int)] = play.api.libs.json.Reads$$anon$8@4c28fa29


or with __ is you like it.

scala> (__(0).read[String] and __(1).read[Int]).tupled
res4: play.api.libs.json.Reads[(String, Int)] = play.api.libs.json.Reads$$anon$8@4c28fa29


scala> res4.reads( Json.arr( "toto", 5 ) )
res6: play.api.libs.json.JsResult[(String, Int)] = JsSuccess((toto,5),)


Hope this helps also!

Pascal


--
 
 

Alex Jarvis

unread,
Jan 23, 2013, 3:55:17 PM1/23/13
to play-fr...@googlegroups.com
Hi Pascal,

Yeah I saw the 'tupled' function in the format combinators blog post and docs, but the example provided there (unlike yours now) wrote/read something that was tupled as a JsObject with each member having its own attribute string as provided (which was surprising seeing as tuples don't naturally have a description for each element).

e.g.
{
  "name": "Pascal",
  "location: {
    "lat": 43.0525,
    "lon": -74.342778
  }
}

instead of:
{
  "name": "Smaller Pascal ;)",
  "location: [43.0525, -74.342778]
  }
}

I guess I just thought it didn't work as I wanted (without modification) and so I skipped over it, but thanks for the extra information!

For now I'm going to stick with defining the reads and writes for Tuple2 (and thus format) implicitly so that they can just be imported. It feels more natural this way and it also allows me to use the macros instead.

Here's the gist of my earlier working example https://gist.github.com/4595298

Do you think it could be included in the Play source directly? I'd be happy to submit a pull-request if you agree.

Alex
Reply all
Reply to author
Forward
0 new messages