Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

case class to json (partial) conversion

I have following case class:

case class Test(name: String, email: String, phone: String)

So in order to be able to serialize to JSON I wrote:

implicit val testWrites: Writes[Test] = (
  (__ \ "name").write[String] and
    (__ \ "email").write[String] and
    (__ \ "phone").write[String]
  )(unlift(Test.unapply))

I want to use this as something like DTO object, so I can exclude some fields while serizalizing. Let's we say I want to show only name and email fields.

I tried something like this:

implicit val testWrites: Writes[Test] = (
  (__ \ "name").write[String] and
    (__ \ "email").write[String]
  )(unlift(Test.unapply))

But this is giving me compile error -> Application does not take parameters.

Does anyone know what is the problem, and how I can achieve mentioned idea?

like image 854
user232343 Avatar asked Dec 14 '22 21:12

user232343


1 Answers

Play JSON combinators often take advantage of the unapply method that is automatically generated in the companion object of a case class.

For your case class:

case class Test(name: String, email: String, phone: String)

The unapply method looks like this:

def unapply(test: Test): Option[(String, String, String)] = Some((test.name, test.email, test.phone))

It returns the field values of the case class tupled and wrapped in Option. For example:

val test: Test = Test("John Sample", "[email protected]", "1-800-NOT-NULL")

Test.unapply(test) // returns Some(("John Sample", "[email protected]", "1-800-NOT-NULL"))

unlift transforms the unapply function into a PartialFunction[Test, (String, String, String)], which is then used to map an instance of Test to a tuple, which is then used to serialize the class.

You need not use Test.unapply. It's only convenient to use it when you want to serialize the entire class. If you only want some fields, you can define a similar function Test => Option[(String, String)]:

def simpleExtractor(test: Test): Option[(String, String)] = Some(test.name, test.email)

And then use it in the JSON combinators:

implicit val testWrites: Writes[Test] = (
    (__ \ "name").write[String] and
    (__ \ "email").write[String]
)(unlift(simpleExtractor))

Similarly, JSON Reads often takes advantage of the automatically generated apply method for case classes. Test.apply _ is a function (String, String, String) => Test-- basically the opposite as unapply, as you might have guessed. The JSON API assembles a tuple with the fields specified in the Reads, then passes that tuple through Test.apply _, which produces the deserialized Test.

To do generate a Reads that will only read two fields you could define another apply-like function:

def simpleBuilder(name: String, email: String): Test = Test(name, email, "default")

implicit val testReads: Reads[Test] = (
    (__ \ "name").read[String] and
    (__ \ "email").read[String]
)(unlift(simpleBuilder _))

Though personally I prefer not to do this, and define a default value within the Reads itself:

implicit val testReads: Reads[Test] = (
    (__ \ "name").read[String] and
    (__ \ "email").read[String] and 
    (__ \ "phone").read[String].orElse(Reads.pure("default"))
)(unlift(Test.apply _))
like image 163
Michael Zajac Avatar answered Jan 02 '23 14:01

Michael Zajac