Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Play Framework 2.1.1 Json Writes nested objects

I've been trying to get my head around this all afternoon to no avail, Play 2.1.1 overhauled how the Json reads and writes work.

Basically I have a wrapper object that looks like:

case class CombinedUser(user: SocialUser, userdetails: UserDetails)

as you can see it contains 2 classes that I want to serialize and deserialize to json.

But I don't understand how I can get and set the fields in the subclasses using the new design.

For example

implicit val combinedUser2Reads = (
  (__ \ "email").read[String] and
  (__ \ "providerid").read[String] and
  (__ \ "firstname").read[String] and
  (__ \ "lastname").read[String] and
  (__ \ "fullname").read[String] and
  (__ \ "avatarurl").read[String] and
  (__ \ "address1").read[String] and
  (__ \ "address2").read[String] and
  (__ \ "address3").read[String] and
  (__ \ "city").read[String] and
  (__ \ "country").read[String] and
  (__ \ "phone").read[String] and
  (__ \ "publickey").as[String]
)(CombinedUser2.apply _)

I want a json blob that has most of the subclasses fields, all strings.

Because it uses the apply stuff, I don't see how I can create the subclasses before the mapping.

Any help or guidance is much appreciated.

Thanks

Tom

like image 522
magicaltrout Avatar asked Feb 17 '23 19:02

magicaltrout


1 Answers

You can do it this way:

case class SocialUser(firstName: String, lastName: String)
case class UserDetails(avatarUrl: String, phone: String)
case class CombinedUser(user: SocialUser, userDetails: UserDetails)  
implicit val combinedUserReads: Reads[CombinedUser] = (
  (__ \ "user").read((
    (__ \ "firstName").read[String] and
    (__ \ "lastName").read[String]
  )(SocialUser)) and
  (__ \ "userDetails").read((
    (__ \ "avatarUrl").read[String] and
    (__ \ "phone").read[String]
  )(UserDetails))
)(CombinedUser)

However its better to create separated Reads:

implicit val socialUserReads = (
  (__ \ "firstName").read[String] and
  (__ \ "lastName").read[String]
)(SocialUser)    
implicit val userDetailsReads = (
  (__ \ "avatarUrl").read[String] and
  (__ \ "phone").read[String]
)(UserDetails)
implicit val combinedUserReads: Reads[CombinedUser] = (
  (__ \ "user").read[SocialUser] and
  (__ \ "userDetails").read[UserDetails]
)(CombinedUser)

Edit: for simple case classes, its possible to do:

implicit val socialUserReads = Json.format[SocialUser]
implicit val userDetailsReads = Json.format[UserDetails]
implicit val combinedUserReads = Json.format[CombinedUser]

Here is quite comprehensive introduction to JSON Reads and more.

How about partial objects? If I don't want to fill in every field in the constructor, can I pass empties or do I overload the constructor or similar?

Use Option:

case class CombinedUser(user: SocialUser, userDetails: Option[UserDetails])
//reads
implicit val combinedUserReads: Reads[CombinedUser] = (
  (__ \ "user").read[SocialUser] and
  (__ \ "userDetails").readOpt[UserDetails]
)(CombinedUser)
//writes
implicit val combinedUserWrites: Writes[CombinedUser] = (
  //socialUserWrites and userDetailsWrites must be in scope
  (__ \ "user").write[SocialUser] and
  (__ \ "userDetails").write[Option[UserDetails]]
)(unlift(CombinedUser.unapply))

val json = Json.obj(
  "user" -> Json.obj(
    "firstName" -> "Homer",
    "lastName" -> "Simpson"
  )
)
Json.fromJson[CombinedUser](json)
//JsSuccess(CombinedUser(SocialUser(Homer,Simpson),None),)
like image 188
Infinity Avatar answered Feb 28 '23 00:02

Infinity