Scala case having 22 fields but having issue with play-json in scala 2.11.5
Asked Answered
F

2

29

With Scala 2.11, we are allowed to have more then 22 fields in a case class right??

case class SomeResponse(
                                     var compositeKey: String,
                                     var id1: String,
                                     var id2: String,
                                     var firstName: String,
                                     var lastName: String,
                                     var email: String,
                                     var email2: String,
                                     var birth: Long,
                                     var gender: String,
                                     var phone: Phone,
                                     var city: String,
                                     var zip: String,
                                     var carriage: Boolean,
                                     var carriage2: Boolean,
                                     var fooLong: Long,
                                     var fooLong2: Long,
                                     var suspended: Boolean,
                                     var foo: Foo,
                                     var address: String,
                                     var suite: String,
                                     var state: String,
                                     var instructions: String)

implicit val formatSomeResponse = Json.format[SomeResponse]

Well the above is a case class which has exactly 22 fields with the play-json format, now when I compile, I get this error:

SomeFile.scala:126: value apply is not a member of play.api.libs.functional.FunctionalBuilder[play.api.libs.json.OFormat]#CanBuild22[String,String,String,String,String,String,String,Long,String,com.Phone,String,String,Boolean,Boolean,Long,Long,Boolean,com.Foo,String,String,String,String]

And the case class Phone and Foo, each have two fields each.

So, why am I actually facing the issue, it doesn't cross the 22 fields limit or is there something else I did wrong, and I tried it in scala 2.11.5/2.11.1 - play-json 2.3

Update: Based on answers by James and Phadej

  val someResponseFirstFormat: OFormat[(String, String, String, String, String, String, String, Long, String, Phone, String)] =
    ((__ \ "compositeKey").format[String] and
      (__ \ "id1").format[String] and
      (__ \ "id2").format[String] and
      (__ \ "firstName").format[String] and
      (__ \ "lastName").format[String] and
      (__ \ "email").format[String] and
      (__ \ "email2").format[String] and
      (__ \ "birth").format[Long] and
      (__ \ "gender").format[String] and
      (__ \ "phone").format[Phone] and
      (__ \ "city").format[String]).tupled

  val someResponseSecondFormat: OFormat[(String, Boolean, Boolean, Long, Long, Boolean, Foo, String, String, String, String)] =
    ((__ \ "zip").format[String] and
      (__ \ "carriage").format[Boolean] and
      (__ \ "carriage2").format[Boolean] and
      (__ \ "fooLong").format[Long] and
      (__ \ "fooLong2").format[Long] and
      (__ \ "suspended").format[Boolean] and
      (__ \ "foo").format[Foo] and
      (__ \ "address").format[String] and
      (__ \ "suite").format[String] and
      (__ \ "country").format[String] and
      (__ \ "instructions").format[String]).tupled

  implicit val formatSome: Format[SomeResponse] = (
    someResponseFirstFormat and someResponseSecondFormat
    ).apply({
    case ((compositeKey, id1, id2, firstName, lastName, email, email2, birth, gender, phone, city),
    (zip, carriage, carriage2, created, updated, suspended, foo, address, suite, country, instructions)) =>
      SomeResponse(compositeKey, id1, id2, firstName, lastName, email, email2, birth, gender, phone, city, zip, carriage, carriage2, created, updated, suspended, location, address, suite, country, instructions)
  }, huge => ((huge.compositeKey, huge.id1, huge.id2, huge.firstName, huge.lastName, huge.email, huge.email2, huge.birth, huge.gender, huge.phone, huge.city),
    (huge.zip, huge.carriage, huge.carriage2, huge.created, huge.updated, huge.suspended, huge.foo, huge.address, huge.suite, huge.country, huge.instructions)))
Fisher answered 27/1, 2015 at 10:16 Comment(3)
Sounds like this is a problem with play-json rather than with scala. At a complete guess, it's possible play still respects the limit if play still has to support scala 2.10?Bradford
Yes, its explained in this SO post #23572177 , but the thing is I haven't crossed the 'more than 22' limit. So, according to play-json, it needs to be less than 22?Fisher
this must be hell...Hilarity
U
22

You can split your Reads definition:

val fields1to10: Reads[(A,B,C,D,E,F,G,H,I,J)] = ???
val fields11to20 = ???
val fields21to30 = ???

implicit val hugeCaseClassReads: Reads[HugeCaseClass] = (
  fields1to10 and fields11to20 and fields21to30
) { a, b, c => createHugeCaseClassFromThreeTuples(a, b, c) }

The reason why "functional syntax" doesn't work for more than 22 fields is because there are intermediate classes defined only up to 22: FunctionalBuilder


Fully written out for small example it would look like:

import play.api.libs.json._
import play.api.libs.functional.syntax._

// Let's pretend this is huge:
case class Huge(a: Int, b: String, c: Boolean, d: List[Int])

val fields1to2: Reads[(Int, String)] = (
  (__ \ "a").read[Int] and
  (__ \ "b").read[String]
).tupled

val fields3to4: Reads[(Boolean, List[Int])] = (
  (__ \ "c").read[Boolean] and
  (__ \ "d").read[List[Int]]
).tupled

implicit val hugeCaseClassReads: Reads[Huge] = (
  fields1to2 and fields3to4
) {
  case ((a, b), (c, d)) =>  
    Huge(a, b, c, d)
}

And the result of tryint to validate null:

scala> JsNull.validate[Huge]
res6: play.api.libs.json.JsResult[Huge] = JsError(
  List(
    (/b,List(ValidationError(error.path.missing,WrappedArray()))),
    (/d,List(ValidationError(error.path.missing,WrappedArray()))),
    (/c,List(ValidationError(error.path.missing,WrappedArray()))),
    (/a,List(ValidationError(error.path.missing,WrappedArray())))))

As you can see, all fields are tried.


Or you could extend play with even more CanBuildNN classes: https://github.com/playframework/playframework/blob/2.3.6/framework/src/play-functional/src/main/scala/play/api/libs/functional/Products.scala


Yet I'd advice you to group fields in the SomeResponse class, for example address related etc. And write Reads and Writes instances by hand, if the JSON structure is flat and cannot be changed.

Unsettled answered 27/1, 2015 at 10:35 Comment(15)
Can you provide a usage example, I tried doing as you have suggested, but can't get a proper answerFisher
CanBuild22 is already provided on master, CanBuild23 and greater is impossible, because they would require Tuple23 and Function23, which don't exist.Haldis
@JamesRoper can't they be defined by user too? or is there some JVM restrictions?Unsettled
I'm not sure about that, maybe they could be. Though that wouldn't help with case classes, as the Scala compiler won't generate the apply/unapply methods on case class companion objects with more than 22 fields, do you'd also have to implement them manually yourself to get the play json macros working.Haldis
@JamesRoper that's why I recommend to group fields, so you don't go over 22 limit or even near it :)Unsettled
Thanks for the replies, but I can't change the response json structure cause I have no jurisdiction to do so. As the response is from a third part api. But anyways, for now the issue is tackled.Fisher
@Unsettled Can I use this way on play.api.libs.json.Format? I got an error "value and is not a member of play.api.libs.json.Format".Mchail
@angelokh, yes. But you have to write both Reads (as above) and Writes (could be written directly) parts of Format by hand.Unsettled
@Unsettled Sorry, I am not quite sure I understand. Can you please give some examples?Mchail
@Mchail you can make Format[A] from Reads[A] and Writes[A] by using apply on the Format object: playframework.com/documentation/2.3.5/api/scala/…Unsettled
@Unsettled I try to combine writes for Huge. But it seems I have to create a json output instead of creating a Huge object. Am I right? Also, if I am already using reads and writes, why do I need to create Format? The reason to use Format is to save time writing Reads and Writes both.Mchail
I don't think this works any more, at least not with play-json 2.4.3Belda
@Unsettled I can't have this working. Copying and pasting your example in my project I got this error Error:(19, 4) missing parameter type for expanded function The argument types of an anonymous function must be fully known. (SLS 8.5) Expected type was: ? ) { ^Attainment
Hi guys! I see that it's possible to combine reads for big objects, however, I don't see how to combine Writes` or Format If I have to implement Writes on my own, it doesn't make sense to do such hacks just for reads...Niemeyer
Does the limitation mean that having a case class with > 22 parameters is a bad design?Lan
C
14

To make the example above compile, I had to make the type explicit:

import play.api.libs.json._
import play.api.libs.functional.syntax._

// Let's pretend this is huge:
case class Huge(a: Int, b: String, c: Boolean, d: List[Int])

object Huge {
  val fields1to2: Reads[(Int, String)] = (
    (__ \ "a").read[Int] and
    (__ \ "b").read[String]
  ).tupled

  val fields3to4: Reads[(Boolean, List[Int])] = (
    (__ \ "c").read[Boolean] and
    (__ \ "d").read[List[Int]]
  ).tupled

  val f: ((Int, String), (Boolean, List[Int])) => Huge = {
    case ((a, b), (c, d)) => Huge(a, b, c, d)
  }

  implicit val hugeCaseClassReads: Reads[Huge] = (
    fields1to2 and fields3to4
  ) { f }

}
Colier answered 9/9, 2016 at 10:13 Comment(1)
Could you give a full example for a OWrites/OFormat? There is one here but it does not compile on 2.5.9+Katelynnkaterina

© 2022 - 2024 — McMap. All rights reserved.