BugsBunny
BugsBunny

Reputation: 99

PlayJson Scala Read Nullable Fields

I have a contact class,
it reads (JsPath \ "contact_name" \ "first_name" ) to firstName, but (JsPath \ "contact_name") can be empty.
Does anyone know how to do the Reader for this case class?

case class Contact(var firstName: Option[String],
                   var lastName: Option[String])

And my contact Json is:

{
  "contact_name": {
     "first_name": "hello",
     "last_name": "world"
  },
  "phone_number": "1231231234",
  "email": "test@gmail.com"
}

Contact Json without "contact_name":

{
  "phone_number": "1231231234",
  "email": "test@gmail.com"
}

I want Both Json to be able to read to Contact objects. Thank you.

Upvotes: 0

Views: 1631

Answers (3)

Jono
Jono

Reputation: 948

To simplify things I've done something like this (although it does create a lot of classes):

Assumptions: contact_name is optional and you want to collapse the whole thing into a single case class Contact.

{
  "contact_name": {
     "first_name": "hello",
     "last_name": "world"
  },
  "phone_number": "1231231234",
  "email": "test@gmail.com"
}


case class Contact(firstName: Optional[String], lastName: Optional[String], phoneNumber: String, email: String)

case class RawContactName(firstName: String, lastName: String)
case class RawContact(contactName: Optional[RawContactName], phoneNumber: String, email: String)

implicit val rawContactReads: Reads[RawContact] = (
      (JsPath \ "contact_name").readNullable[RawContactName] and
      (JsPath \ "phone_number").read[String] and
      (JsPath \ "email").read[String]
    ) (RawContact.apply _)

implicit val rawContactNameReads: Reads[RawContactName] = (
      (JsPath \ "first_name").read[String] and
      (JsPath \ "last_name").read[String]
    ) (RawContactName.apply _)

def transformContact(rawContact: RawContact): Contact = {
    val (maybeFirstName, maybeLastName) = rawContact.contactName match {
        case Some(RawContactName(firstName, lastName)) => (Some(firstName), Some(lastName))
        case None => (None, None)
    }
    Contact(maybeFirstName, maybeLastName, rawContact.phoneNumber, rawContact.email)
}

Effectively, I have separate case classes to represent each of the JSON nodes and a transformer function to transform the Scala JSON representation into my model class. This is effective if you have repeated values (example: multiple contact objects in the same JSON document, so multiple first_name elements would appear). Although in your specific example it might be better to skip the Raw classes and create two case classes inside your model instead: Contact and ContactName but I wanted to demonstrate a general solution which separates the View model (Raw...) from the internal Model.

Upvotes: 0

pedrorijo91
pedrorijo91

Reputation: 7865

I did write a post on that - http://pedrorijo.com/blog/scala-json/

It approaches how to read json to case classes. Specifically, reading optional fields from json, on the last example:

case class User(username: String, friends: Int, enemies: Int, isAlive: Option[Boolean])

object User {

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

  implicit val userReads: Reads[User] = (
      (JsPath \ "username").read[String] and
      (JsPath \ "friends").read[Int] and
      (JsPath \ "enemies").read[Int] and
      (JsPath \ "is_alive").readNullable[Boolean]
    ) (User.apply _)
}

It should be enough for you to get it done. Also, if json is a string with the desired fields, you can just write:

Json.parse(json)

so, if fullJson is the full Json object (with those undesired fields) you can just extract the firstName and lastName with fullJson \ "contact_name"

Upvotes: 0

Mikesname
Mikesname

Reputation: 8901

Assuming your phone number and email are part of the contact details, here's one that works (you can use \\ to search for paths in depth):

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

case class Contact(firstName: Option[String], lastName: Option[String],
                    phone: String, email: String)

val contactReads: Reads[Contact] = (
  (__ \\ "first_name").readNullable[String] and
  (__ \\ "last_name").readNullable[String] and
  (__ \ "phone_number").read[String] and
  (__ \ "email").read[String]
)(Contact.apply _)

val json1 = """{
   | "contact_name": {
   |    "first_name": "hello",
   |    "last_name": "world"
   |  },
   |  "phone_number": "1231231234",
   |  "email": "test@gmail.com"
   |}""".stripMargin

Json.parse(json1).validate[Contact](contactReads)
// JsSuccess(Contact(Some(hello),Some(world),1231231234,test@gmail.com),)

val json2 = """{
   |  "phone_number": "1231231234",
   |  "email": "test@gmail.com"
   |}""".stripMargin

Json.parse(json2).validate[Contact](contactReads)
// JsSuccess(Contact(None,None,1231231234,test@gmail.com),)

Upvotes: 3

Related Questions