Robert Gabriel
Robert Gabriel

Reputation: 1190

Is there a way to map a JSON object with more then 22 keys in Scala (2.12.4) Play2 (2.6.3)?

Just a simple example, I want to map a JSON response to a Scala case class. I'm using Play2 framework 2.6.3.

case class Hobby(id:Int, name:String)
case class Person(name:String,
              name1:String,
              name2:String,
              name3:String,
              name4:String,
              name5:String,
              name6:String,
              name7:String,
              name8:String,
              name9:String,
              name10:String,
              name11:String,
              name12:String,
              name13:String,
              name14:String,
              name15:String,
              name16:String,
              name17:String,
              name18:String,
              name19:String,
              name20:String,
              nickname:Boolean, hobbies:Seq[Hobby], createdDate:LocalDateTime)

I know that I could create a reader, Reads[Person] and a Reads[Hobby], or parse manually each field like in this example: https://github.com/playframework/play-json

My question is if I can create a sort of automatic parser using a implicit value, because I have more then 22 fields, and it reaches Scala maximum Tuple22.

JSON example of Person object:

    {
  "name": "Mary",
  "name1": "Mary",
  "name2": "Mary",
  "name3": "Mary",
  "name4": "Mary",
  "name5": "Mary",
  "name6": "Mary",
  "name7": "Mary",
  "name8": "Mary",
  "name9": "Mary",
  "name10": "Mary",
  "name11": "Mary",
  "name12": "Mary",
  "name13": "Mary",
  "name14": "Mary",
  "name15": "Mary",
  "name16": "Mary",
  "name17": "Mary",
  "name18": "Mary",
  "name19": "Mary",
  "name20": "Mary",
  "nickname": true,
  "hobbies" : [
    {
      "id": 1,
      "name": "fitness",
      "createdDate": "2018-03-29T17:49:24.5304566+07:00"
    },
    {
      "id": 2,
      "name": "skating",
      "createdDate": "2018-03-29T17:49:24.5304566+07:00"
    }
  ],
  "createdDate": "2018-03-29T17:49:24.5304566+07:00"
}

I managed to reproduce the error that I got, it appears because in the JSON that I was receiving I'm exceeding the Tuple22 in Scala, that is the maximum tuple. This is the error that I was getting:

Error:(62, 44) No unapply or unapplySeq function found for class Person: <none> / <none>
      implicit val personReads = Json.reads[Person]

Upvotes: 1

Views: 787

Answers (3)

Pierre
Pierre

Reputation: 2902

For a 2022 answer: I also had this problem (2.13.6 and playframework 2.8.8) and fixed it using, in some cases, option1, and other cases option2 below. The error that would come up when I had more than 22 fields was:

No unapply or unapplySeq function found for class : <none> / <none>

Option1: stick to 22 fields or less in your case class. In my code example below, I could introduce new fields (ex: Topic, Requester, Recipient fields) to encapsulate a bunch of fields, thus reducing the number of fields to well below 22.

Option2: if you must have more than 22 fields, you can use the JsonX play extension. See: https://github.com/bizzabo/play-json-extensions

For this, first add the dependency to your build.sbt:

// to support more than 22 fields in a case class
libraryDependencies += "ai.x" %% "play-json-extensions" % "0.42.0"

And then, in your case class, use JsonX, like this:

package model

import play.api.libs.json.{Format, Json, OFormat}
import ai.x.play.json.Encoders.encoder
import ai.x.play.json.{BaseNameEncoder, Jsonx, NameEncoder}

case class MyCaseClass(someId: Int,
                              someOtherId: Int,
                              personId: Int,
                              currentStatus: String,
                              currentStatusDesc: String,
                              submittedTimestamp: Long,
                              someCategoryId: Int,
                              someCategoryParentId: Int,
                              someCategoryName: String,
                              someCategoryCode: Int, 
                              topicIsActive: Boolean,
                              topicIsGeneralTopic: Boolean,
                              topicCategorySortOrder: Int,
                              topicIconCssClass: String,
                              recipientPersonId: Option[Int],
                              recipientEmail: Option[String],
                              recipientFirstName: Option[String],
                              recipientLastName: Option[String],
                              recipientExtId: Option[String],
                              requesterSurveyId: Option[Int],
                              requesterSurveyResponse: Option[String],
                              recipientSurveyId: Option[Int],
                              recipientSurveyResponse: Option[String]
                              )
object MyCaseClass{
  // IMPORTANT: because the case class (MyCaseClass) has more than 22 fields, we can't use the default Json.Format
  implicit val f: OFormat[MyCaseClass] = Jsonx.formatCaseClass[MyCaseClass]
}

And voilà! You may now transform to JSON more than 22 fields in your case class.

Upvotes: 0

Robert Gabriel
Robert Gabriel

Reputation: 1190

I found a library that can automatically parse more complex JSONs.

https://circe.github.io/circe/

For LocalDateTime I need to use this:

implicit val encodeFoo: Encoder[LocalDateTime] = (a: LocalDateTime) => {
    Json.fromString(a.format(DateTimeFormatter.ISO_DATE_TIME))
}
implicit val decodeFoo: Decoder[LocalDateTime] = (c: HCursor) => {
   c.value.as[String]
    .map(LocalDateTime.parse(_, DateTimeFormatter.ISO_DATE_TIME))
}

And just use decode method on the JSON string:

val person: Person = decode[Person](jsonStr)

Upvotes: 1

Feyyaz
Feyyaz

Reputation: 3206

You don't have to manually create the implicit Reads/Writes objects. Play JSON library handles that.

Deserialize JSON String to object:

import play.api.libs.json._

case class Hobby(id:Int, name:String)
case class Person(name:String, nickname:String, hobbies:Seq[Hobby], createdDate:LocalDate)

implicit val hobbyReads = Json.reads[Hobby]
implicit val personReads = Json.reads[Person]

val person: Person = Json.parse(jsonString).as[Person]

Serialize back to JSON String:

implicit val hobbyWrites = Json.writes[Hobby]
implicit val personWrites = Json.writes[Person]

Json.toJson(person).toString()

Writing a custom Reads/Writes would only be required if you need to make a custom calculation/manipulation on the input.

Upvotes: 0

Related Questions