Json4s: Trouble while trying to convert Json attribute to java.sql.Date - scala

I am using Json4s to deserialize json messages. I have a case class like
case class A(id: Int, b: Option[java.sql.Date])
Whenever I try to convert a json message to case class A, I get none as the value of b
scala> read[A]("""{"id":1,"b":12345}""")
res2: A = A(1,None)
scala> read[A]("""{"id":1,"b":"12345"}""")
res3: A = A(1,None)
scala> read[A]("""{"id":1,"b":"12/12/2014"}""")
res4: A = A(1,None)
How can I fix this issue

Something along these lines (you might want to be more specific with formats). And then mix this trait in the classes which need to have access to this custom serializer.
import org.json4s.DefaultJsonFormats._
trait JsonFormats {
case object DateSerializer extends CustomSerializer[java.sql.Date](format => (
{
case JString(s) => Date.valueOf(s)
case JNull => null
},
{
case d: Date => JString(d.toString())
}
)
)
implicit val json4sFormats = native.Serialization.formats(NoTypeHints) + DateSerializer
}

Related

no instance of play.api.libs.json.format is available for scala.option[scala.any]

I just trying to create Json format (using play json) of a case class which contains a field of Any data type in Scala
import play.api.libs.json.{Format, Json}
case class Basics(
label:Option[String],
key:Option[String],
name:Option[String],
value:Option[Any],
field_type:Option[String]
)
object Basics{
implicit val format:Format[Basics]=Json.format
}
When I compiling my code it says that..
no instance of play.api.libs.json.format is available for scala.option[scala.any]
I am taking Any type because the 'value' field might be a String or a Boolean Type.
Any suggestion that I am missing something?
Found a solution for it..
object Basics{
implicit val anyReads: Reads[Any] = Reads[Any](m => JsonFormatConverter.metaValueToJsValue(m))
implicit val WritesAny: Writes[Any] = JsonFormatConverter.anyWrite
implicit val format:Format[Basics] = Json.format[Basics]
}
object JsonFormatConverter {
def metaValueToJsValue(m: JsValue): JsResult[Any] = {
m match {
case JsString(s) => JsSuccess(s)
case JsBoolean(b) => JsSuccess(b)
}
}
val anyWrite:Writes[Any]=Writes[Any]{
res=>
res match {
case x: String => JsString(x)
case x: Boolean => JsBoolean(x)
}
}
}

json4s "Can't convert JString(2019-04-28T01:23:45.678Z) to class java.time.Instant"?

I'm struggling to get json4s to work with java.time.Instant values in my case class. The REPL looks like this.
scala> case class TestTime(tag: String, t: java.time.Instant)
defined class TestTime
scala> import org.json4s._
import org.json4s._
scala> import org.json4s.ext.JavaTimeSerializers
import org.json4s.ext.JavaTimeSerializers
scala> import org.json4s.native.JsonMethods._
import org.json4s.native.JsonMethods._
scala> implicit lazy val formats = DefaultFormats ++ JavaTimeSerializers.all
formats: org.json4s.Formats = <lazy>
scala> val parsed = parse("""{"tag": "second","t": "2019-04-28T01:23:45.678Z"}""")
parsed: org.json4s.JValue = JObject(List((tag,JString(second)), (t,JString(2019-04-28T01:23:45.678Z))))
scala> val second = parsed.extract[TestTime]
org.json4s.package$MappingException: No usable value for t
Can't convert JString(2019-04-28T01:23:45.678Z) to class java.time.Instant
at org.json4s.reflect.package$.fail(package.scala:95)
at org.json4s.Extraction$ClassInstanceBuilder.org$json4s$Extraction$ClassInstanceBuilder$$buildCtorArg(Extraction.scala:569)
at org.json4s.Extraction$ClassInstanceBuilder$$anonfun$3.applyOrElse(Extraction.scala:593)
at org.json4s.Extraction$ClassInstanceBuilder$$anonfun$3.applyOrElse(Extraction.scala:591)
at scala.PartialFunction.$anonfun$runWith$1$adapted(PartialFunction.scala:145)
at scala.collection.mutable.ResizableArray.foreach(ResizableArray.scala:62)
at scala.collection.mutable.ResizableArray.foreach$(ResizableArray.scala:55)
at scala.collection.mutable.ArrayBuffer.foreach(ArrayBuffer.scala:49)
at scala.collection.TraversableLike.collect(TraversableLike.scala:274)
at scala.collection.TraversableLike.collect$(TraversableLike.scala:272)
at scala.collection.AbstractTraversable.collect(Traversable.scala:108)
at org.json4s.Extraction$ClassInstanceBuilder.instantiate(Extraction.scala:591)
at org.json4s.Extraction$ClassInstanceBuilder.result(Extraction.scala:651)
at org.json4s.Extraction$.$anonfun$extract$10(Extraction.scala:410)
at org.json4s.Extraction$.$anonfun$customOrElse$1(Extraction.scala:658)
at scala.PartialFunction.applyOrElse(PartialFunction.scala:127)
at scala.PartialFunction.applyOrElse$(PartialFunction.scala:126)
at scala.PartialFunction$$anon$1.applyOrElse(PartialFunction.scala:257)
at org.json4s.Extraction$.customOrElse(Extraction.scala:658)
at org.json4s.Extraction$.extract(Extraction.scala:402)
at org.json4s.Extraction$.extract(Extraction.scala:40)
at org.json4s.ExtractableJsonAstNode.extract(ExtractableJsonAstNode.scala:21)
... 36 elided
Caused by: org.json4s.package$MappingException: Can't convert JString(2019-04-28T01:23:45.678Z) to class java.time.Instant
at org.json4s.CustomSerializer$$anonfun$deserialize$2.applyOrElse(Formats.scala:450)
at org.json4s.CustomSerializer$$anonfun$deserialize$2.applyOrElse(Formats.scala:447)
at org.json4s.Extraction$.customOrElse(Extraction.scala:658)
at org.json4s.Extraction$.extract(Extraction.scala:402)
at org.json4s.Extraction$ClassInstanceBuilder.org$json4s$Extraction$ClassInstanceBuilder$$buildCtorArg(Extraction.scala:554)
... 56 more
scala> val instant = java.time.Instant.parse("2014-12-03T10:15:30.00Z")
instant: java.time.Instant = 2014-12-03T10:15:30Z
How do I get rid of that stacktrace and get this extraction to work?
Looking at the source, the custom serializer for Instant will only parse Int values, not String values:
case object JInstantSerializer extends CustomSerializer[Instant]( format => (
{
case JInt(d) => Instant.ofEpochMilli(d.toLong)
case JNull => null
},
{
case d: Instant => JInt(d.toEpochMilli)
}
))
You can add a rule to parse String to Instant to fix the problem.
case object MyInstantSerialzer extends CustomSerializer[Instant]( format => (
{
case JInt(d) => Instant.ofEpochMilli(d.toLong)
case JString(s) => Instant.parse(s)
case JNull => null
},
{
case d: Instant => JInt(d.toEpochMilli)
}
))
implicit lazy val formats = DefaultFormats + MyInstantSerialzer
Personally I think this is broken because it does not preserve the accuracy of Instant when read/writing to JSON. This feels better:
case object MyInstantSerialzer extends CustomSerializer[Instant]( format => (
{
case JString(s) => Instant.parse(s)
case JNull => null
},
{
case s: Instant => JString(s.toString)
}
))
There is also a more complicated solution in the answer to this question

Type Option[java.sql.Timestamp] cannot be used as a default value

Trying to get a derived input object from a case class.
val UserInputType = deriveInputObjectType[UserRow]()
case class UserRow(id: Int, name: String, lastModifiedTime: Option[java.sql.Timestamp] = None)
but getting this following error
Type Option[java.sql.Timestamp] cannot be used as a default value. Please consider defining an implicit instance of `ToInput` for it.
I have also defined the type for the timestamp:
case object DateTimeCoerceViolation extends Violation {
override def errorMessage: String = "Error during parsing DateTime"
}
def parseTime(s: String) = Try(Timestamp.valueOf(s)) match {
case Success(date) ⇒ Right(date)
case Failure(_) ⇒ Left(DateTimeCoerceViolation)
}
implicit val TimestampType = ScalarType[Timestamp](
"Timestamp",
coerceOutput = (ts, _) => ts.getTime,
coerceInput = {
case StringValue(ts, _, _, _,_) => parseTime(ts)
case _ => Left(DateTimeCoerceViolation)
},
coerceUserInput = {
case s: String => parseTime(s)
case _ => Left(DateTimeCoerceViolation)
}
)
How can solve this ?
As the error says - Please consider defining an implicit instance of ToInput for it, you have to provide ToInput for Timestamp implicitly. Because, without it, sangria will not be able to serialize raw data to Timestamp.
implicit val toInput = new ToInput[Option[java.sql.Timestamp], Json] {
override def toInput(value: Option[Timestamp]): (Json, InputUnmarshaller[Json]) = value match {
case Some(time) => (Json.fromString(time.toString), sangria.marshalling.circe.CirceInputUnmarshaller)
case None => (Json.Null, sangria.marshalling.circe.CirceInputUnmarshaller)
}
}
On the other hand, if are using play-json library, you don't have to explicitly define and use toInput.
For example, in play json, you can define implicit format for TimeStamp.
implicit val timeStampFormat: Format = ???
Above format will be implicitly used by sangria Json-Marshaller for serializing Json to Timestamp.

Circe: force an optional field to `null`

Is there a way to serialize a single None field to "null" ?
For example:
// When None, I'd like to serialize only f2 to `null`
case class Example(f1: Option[Int], f2: Option[Int])
val printer = Printer.noSpaces.copy(dropNullValues = true)
Example(None, None).asJson.pretty(printer) === """{"f2":null}"""
You can do this pretty straightforwardly by mapping a filter over the output of an encoder (which can be derived, defined with Encoder.forProductN, etc.):
import io.circe.{ Json, ObjectEncoder }
import io.circe.generic.semiauto.deriveEncoder
case class Example(f1: Option[Int], f2: Option[Int])
val keepSomeNulls: ((String, Json)) => Boolean = {
case ("f1", v) => !v.isNull
case (_, _) => true
}
implicit val encodeExample: ObjectEncoder[Example] =
deriveEncoder[Example].mapJsonObject(_.filter(keepSomeNulls))
And then:
scala> import io.circe.syntax._
import io.circe.syntax._
scala> Example(Some(1), Some(2)).asJson.noSpaces
res0: String = {"f1":1,"f2":2}
scala> Example(Some(1), None).asJson.noSpaces
res1: String = {"f1":1,"f2":null}
scala> Example(None, Some(2)).asJson.noSpaces
res2: String = {"f2":2}
scala> Example(None, None).asJson.noSpaces
res3: String = {"f2":null}
Note that configuring a printer to drop null values will still remove "f2": null here. This is part of the reason I think in general it's best to make the preservation of null values solely the responsibility of the printer, but in a case like this, the presence or absence of null-valued fields is clearly semantically meaningful, so you kind of have to mix up the levels.

Scala 2.10 reflection, how do I extract the field values from a case class, i.e. field list from case class

How can I extract the field values from a case class in scala using the new reflection model in scala 2.10?
For example, using the below doesn't pull out the field methods
def getMethods[T:TypeTag](t:T) = typeOf[T].members.collect {
case m:MethodSymbol => m
}
I plan to pump them into
for {field <- fields} {
currentMirror.reflect(caseClass).reflectField(field).get
}
MethodSymbol has an isCaseAccessor method that allows you to do precisely this:
def getMethods[T: TypeTag] = typeOf[T].members.collect {
case m: MethodSymbol if m.isCaseAccessor => m
}.toList
Now you can write the following:
scala> case class Person(name: String, age: Int)
defined class Person
scala> getMethods[Person]
res1: List[reflect.runtime.universe.MethodSymbol] = List(value age, value name)
And you get only the method symbols you want.
If you just want the actual field name (not the value prefix) and you want them in the same order then:
def getMethods[T: TypeTag]: List[String] =
typeOf[T].members.sorted.collect {
case m: MethodSymbol if m.isCaseAccessor => m.name.toString
}
If you want to get fancier you can get them in order by inspecting the constructor symbol. This code works even if the case class type in question has multiple constructors defined.
import scala.collection.immutable.ListMap
import scala.reflect.runtime.universe._
/**
* Returns a map from formal parameter names to types, containing one
* mapping for each constructor argument. The resulting map (a ListMap)
* preserves the order of the primary constructor's parameter list.
*/
def caseClassParamsOf[T: TypeTag]: ListMap[String, Type] = {
val tpe = typeOf[T]
val constructorSymbol = tpe.decl(termNames.CONSTRUCTOR)
val defaultConstructor =
if (constructorSymbol.isMethod) constructorSymbol.asMethod
else {
val ctors = constructorSymbol.asTerm.alternatives
ctors.map(_.asMethod).find(_.isPrimaryConstructor).get
}
ListMap[String, Type]() ++ defaultConstructor.paramLists.reduceLeft(_ ++ _).map {
sym => sym.name.toString -> tpe.member(sym.name).asMethod.returnType
}
}