Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I write a JSON Format for an object in the Java library that doesn't have an apply method?

I've been stuck on this particular problem for about a week now, and I figure I'm going to write this up as a question on here to clear out my thoughts and get some guidance.

So I have this case class that has a java.sql.Timestamp field:

case class Request(id: Option[Int], requestDate: Timestamp)

and I want to convert this to a JsObject

val q = Query(Requests).list // This is Slick, a database access lib for Scala
  printList(q)    
  Ok(Json.toJson(q))   // and this is where I run into trouble

"No Json deserializer found for type List[models.Request]. Try to implement an implicit Writes or Format for this type." Okay, that makes sense.

So following the Play documentation here, I attempt to write a Format...

implicit val requestFormat = Json.format[Request]  // need Timestamp deserializer
implicit val timestampFormat = (
      (__ \ "time").format[Long]   // error 1
)(Timestamp.apply, unlift(Timestamp.unapply))  // error 2

Error 1

Description Resource Path Location Type overloaded method value format with alternatives:   

(w: play.api.libs.json.Writes[Long])(implicit r: play.api.libs.json.Reads[Long])play.api.libs.json.OFormat[Long] 
<and>   
(r: play.api.libs.json.Reads[Long])(implicit w: play.api.libs.json.Writes[Long])play.api.libs.json.OFormat[Long] 
<and>   
(implicit f: play.api.libs.json.Format[Long])play.api.libs.json.OFormat[Long]  
cannot be applied to (<error>, <error>)

Apparently importing like so (see the documentation "ctrl+F import") is getting me into trouble:

import play.api.libs.json._    // so I change this to import only Format and fine
import play.api.libs.functional.syntax._
import play.api.libs.json.Json
import play.api.libs.json.Json._  

Now that the overloading error went away, I reach more trubbles: not found: value __ I imported .../functional.syntax._ already just like it says in the documentation! This guy ran into the same issue but the import fixed it for him! So why?! I thought this might just be Eclipse's problem and tried to play run anyway ... nothing changed. Fine. The compiler is always right.

Imported play.api.lib.json.JsPath, changed __ to JsPath, and wallah:

Error 2

value apply is not a member of object java.sql.Timestamp
value unapply is not a member of object java.sql.Timestamp


I also try changing tacks and writing a Write for this instead of Format, without the fancy new combinator (__) feature by following the original blog post the official docs are based on/copy-pasted from:

// I change the imports above to use Writes instead of Format
 implicit val timestampFormat = new Writes[Timestamp](  // ERROR 3
    def writes(t: Timestamp): JsValue = { // ERROR 4 def is underlined
      Json.obj(
          /* Returns the number of milliseconds since 
           January 1, 1970, 00:00:00 GMT represented by this Timestamp object. */
              "time" -> t.getTime() 
      )
    }
  )

ERROR 3: trait Writes is abstract, cannot be instantiated

ERROR 4: illegal start of simple expression

At this point I'm about at my wits' end here, so I'm just going back to the rest of my mental stack and report from my first piece of code



My utter gratefulness to anybody who can put me out of my coding misery

like image 752
Meredith Avatar asked Jun 24 '13 17:06

Meredith


People also ask

What is JSON object in Java with example?

JSON (JavaScript Object Notation) is a lightweight, text-based, language-independent data exchange format that is easy for humans and machines to read and write. JSON can represent two structured types: objects and arrays. An object is an unordered collection of zero or more name/value pairs.

Can we convert object to JSON in Java?

Converting Java object to JSON In it, create an object of the POJO class, set required values to it using the setter methods. Instantiate the ObjectMapper class. Invoke the writeValueAsString() method by passing the above created POJO object. Retrieve and print the obtained JSON.


1 Answers

It's not necessarily apply or unapply functions you need. It's a) a function that constructs whatever the type you need given some parameters, and b) a function that turns an instance of that type into a tuple of values (usually matching the input parameters.)

The apply and unapply functions you get for free with a Scala case class just happen to do this, so it's convenient to use them. But you can always write your own.

Normally you could do this with anonymous functions like so:

import java.sql.Timestamp
import play.api.libs.functional.syntax._
import play.api.libs.json._

implicit val timestampFormat: Format[Timestamp] = (
  (__ \ "time").format[Long]
)((long: Long) => new Timestamp(long), (ts: Timestamp) => (ts.getTime))

However! In this case you fall foul of a limitation with the API that prevents you from writing formats like this, with only one value. This limitation is explained here, as per this answer.

For you, a way that works would be this more complex-looking hack:

import java.sql.Timestamp
import play.api.libs.functional.syntax._
import play.api.libs.json._

implicit val rds: Reads[Timestamp] = (__ \ "time").read[Long].map{ long => new Timestamp(long) }
implicit val wrs: Writes[Timestamp] = (__ \ "time").write[Long].contramap{ (a: Timestamp) => a.getTime }
implicit val fmt: Format[Timestamp] = Format(rds, wrs)

// Test it...
val testTime = Json.obj("time" -> 123456789)
assert(testTime.as[Timestamp] == new Timestamp(123456789))
like image 106
Mikesname Avatar answered Sep 21 '22 00:09

Mikesname