Reputation: 1190
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
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
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
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