Scala (spray) json serialization and deserialization issue - json

I'm using my own implicit implementation of JSON serializer and deserializer for my case object
My case class looks like (it's just a code snippet)
sealed trait MyTrait
case object MyCaseClass extends MyTrait
I want to write my own ser. and deser. of JSON for MyTrait
implicit val myTraitFormat = new JsonFormat[MyTrait] {
override def read(json: JsValue): MyTrait = json.asJsObject.getFields("value") match {
case Seq(JsString("TEST")) ⇒ MyCaseClass
case _ ⇒ throw new DeserializationException(s"$json is not a valid extension of my trait")
}
override def write(myTrait: MyTrait): JsValue = {
myTrait match {
case MyCaseClass => JsObject("value" -> JsString("TEST"))
}
}
}
Now my test is failing by throwing DeserializationException:
"The my JSON format" when {
"deserializing a JSON" must {
"return correct object" in {
val json = """{"value": "TEST"}""".asJson
json.convertTo[MyTrait] must equal (MyCaseClass)
}
}
}
Obviously json.asJsObject.getFields("value")can not be matched to Seq(JsString("TEST")). Maybe this is related to using traits?
But I have found example on official spray-json site https://github.com/spray/spray-json#providing-jsonformats-for-other-types
Any ideas how to properly match on field in JsObject?
Thanks!
Best

Write variable name instead of string:
override def read(json: JsValue): MyCaseClass = json.asJsObject.getFields("value") match {
case Seq(JsString(value)) ⇒ MyCaseClass
case _ ⇒ throw new DeserializationException(s"$json is not a valid case class")
}
Does that work for you?
Update:
Yes, your test is wrong. I tried it with the following (note parseJson instead of asJson) and it worked:
scala> val json = """{"value": "TEST"}""".parseJson
json: spray.json.JsValue = {"value":"TEST"}
scala> json.convertTo[MyCaseClass]
res2: MyCaseClass = MyCaseClass()
Update 2: Tried it with trait:
scala> import spray.json._
import spray.json._
scala> import spray.json.DefaultJsonProtocol._
import spray.json.DefaultJsonProtocol._
[Your code]
scala> val json = """{"value": "TEST"}""".parseJson
json: spray.json.JsValue = {"value":"TEST"}
scala> json.convertTo[MyTrait]
res1: MyTrait = MyCaseClass

Related

json Generic decoder with default values using scala with circe

I have encountered a weird situation.
I m trying to build a method that takes a type and a JSON
and build it into a case class instance and if needed auto-complete missing key values.
So far I managed to do everything separately but not altogether.
The case class with its defaults:
case class Foo(a: String = "empty String", b: Option[Int] = Some(1))
and when I do the conversion:
import io.circe.generic.extras.auto._
import io.circe.generic.extras.Configuration
import io.circe.parser.decode
implicit val customConfig: Configuration = Configuration.default.withDefaults
println(decode[Foo]("{}"))
this is the output I get:
Right(Foo(empty String,Some(1)))
and this is working as I expected
but when I put it into a generic method it required a to be an option due to the error:
Exception in thread "main" java.lang.ExceptionInInitializerError
Caused by: DecodingFailure(Attempt to decode value on failed cursor, List(DownField(a)))
so I`m changing the case class to be
case class Foo(a: Option[String] = Some("empty String"), b: Option[Int] = Some(1))
and add the decoder:
object Foo{
implicit val decoder:Decoder[Foo] = deriveDecoder[Foo]
}
to the method:
import io.circe.Decoder
import io.circe.parser.decode
def convertToObj[T](jsonStr: String)(implicit decoder: Decoder[T]): T = {
decode[T](jsonStr)
match {
case Right(value) => value
case Left(error) => throw error
}
}
println(convertToObj[Foo]("{}"))
and the output is:
Foo(None,None)
so now I have lost my default values that I put and not able to use the automatic decoder as well.
How can I combine my two wishes into one approach?
You would need to do something like:
package foo.bar
import io.circe.Decoder
import io.circe.generic.extras.semiauto
import io.circe.generic.extras.Configuration
import io.circe.parser.decode
case class Foo(a: String = "empty String", b: Option[Int] = Some(1))
object Foo {
implicit val customConfig: Configuration = Configuration.default.withDefaults
implicit val decoder: Decoder[Foo] = semiauto.deriveConfiguredDecoder[Foo]
}
object TestApp extends App {
def convertToObj[T](jsonStr: String)(implicit decoder: Decoder[T]): T =
decode[T](jsonStr) match {
case Right(value) => value
case Left(error) => throw error
}
println(convertToObj[Foo]("{}"))
}
However, you can have circe automatically derive your decoder for you, so you can get away with less boilerplate:
package foo.bar
import io.circe.Decoder
import io.circe.generic.extras.auto._
import io.circe.generic.extras.Configuration
import io.circe.parser.decode
case class Foo(a: String = "empty String", b: Option[Int] = Some(1))
object TestApp extends App {
implicit val customConfig: Configuration = Configuration.default.withDefaults
def convertToObj[T](jsonStr: String)(implicit decoder: Decoder[T]): T =
decode[T](jsonStr) match {
case Right(value) => value
case Left(error) => throw error
}
println(convertToObj[Foo]("{}"))
}
Both of these examples give me output: Foo(empty String,Some(1))
NOTE:
method deriveDecoder in object semiauto is deprecated (since 0.12.0): Use deriveConfiguredDecoder

scala spray.json how to get Json Object

If I try Http Get Response {"ReturnValue":""},
this Code make error.
Caused by: spray.json.DeserializationException: Expected List as
JsArray, but got {"ReturnValue":""}
import spray.httpx.SprayJsonSupport._
import spray.json.DefaultJsonProtocol
import spray.http._
import spray.client.pipelining._
import scala.concurrent.duration._
import scala.concurrent.{ Await, Future }
import akka.actor.ActorSystem
import scala.concurrent.ExecutionContext.Implicits.global
class ApiHelper extends DefaultJsonProtocol {
case class Robot(name: String, color: Option[String], amountOfArms: Int)
implicit val RobotFormat = jsonFormat3(Robot)
def CallAPI(httpMethod: String, subURL: String): String = {
val apiLocation = "~~~"
val timeout = 5.seconds
implicit val system = ActorSystem("robotClient")
return httpMethod match {
case "GET" =>
val pipeline: HttpRequest => Future[List[Robot]] = sendReceive ~> unmarshal[List[Robot]]
val f: Future[List[Robot]] = pipeline(Get(s"$apiLocation"+subURL))
val robots = Await.result(f, timeout)
println(s"Got the list of robots: $robots")
return "hello"
}
}
}
Caused by: spray.json.DeserializationException: Expected List as JsArray, but got {"ReturnValue":""} at
spray.json.package$.deserializationError(package.scala:23) at
spray.json.CollectionFormats$$anon$1.read(CollectionFormats.scala:29)
at
spray.json.CollectionFormats$$anon$1.read(CollectionFormats.scala:25)
at
spray.httpx.SprayJsonSupport$$anonfun$sprayJsonUnmarshaller$1.applyOrElse(SprayJsonSupport.scala:37)
at
spray.httpx.SprayJsonSupport$$anonfun$sprayJsonUnmarshaller$1.applyOrElse(SprayJsonSupport.scala:34)
at
scala.runtime.AbstractPartialFunction.apply(AbstractPartialFunction.scala:36)
at
spray.httpx.unmarshalling.Unmarshaller$$anon$1$$anonfun$unmarshal$1.apply(Unmarshaller.scala:29)
at
spray.httpx.unmarshalling.SimpleUnmarshaller.protect(SimpleUnmarshaller.scala:40)
at
spray.httpx.unmarshalling.Unmarshaller$$anon$1.unmarshal(Unmarshaller.scala:29)
at
spray.httpx.unmarshalling.SimpleUnmarshaller.apply(SimpleUnmarshaller.scala:29)
at
spray.httpx.unmarshalling.SimpleUnmarshaller.apply(SimpleUnmarshaller.scala:23)
at
spray.httpx.unmarshalling.UnmarshallerLifting$$anon$3.apply(UnmarshallerLifting.scala:35)
at
spray.httpx.unmarshalling.UnmarshallerLifting$$anon$3.apply(UnmarshallerLifting.scala:34)
at
spray.httpx.unmarshalling.UnmarshallerLifting$$anon$2.apply(UnmarshallerLifting.scala:30)
at
spray.httpx.unmarshalling.UnmarshallerLifting$$anon$2.apply(UnmarshallerLifting.scala:29)
at
spray.httpx.unmarshalling.package$PimpedHttpResponse.as(package.scala:51)
at
spray.httpx.ResponseTransformation$$anonfun$unmarshal$1.apply(ResponseTransformation.scala:33)
... 13 more
Is there any way to get Json Object?
You can provide and use your own implementation of unmarshal which would construct JsValue instead of List[Robot]. JsValue would represent either valid response (list of robots) or arbitrary json response (or probably more custom object types).
def unmarshal: HttpResponse ⇒ JsValue =
response ⇒
if (response.status.isSuccess)
response.as[List[Robot]] match {
case Right(value) ⇒ value.toJson
case Left(error: MalformedContent) ⇒
response.as[JsObject] match {
case Right(value) ⇒ value.toJson
case Left(error) => throw new PipelineException(error.toString)
}
case Left(error) ⇒ throw new PipelineException(error.toString)
}
else throw new UnsuccessfulResponseException(response.status)
After the future (call to pipeline) returns JsValue you can try to convert it back again to List[Robot] in a controlled way (e.g. within a Try block) and in case of failure handle it as a custom json response.

convert json to array of scala objects using spray json

I am not much familier with spray json, but I have to convert the below json into Array[myTest]
Below is the code, but it doesnt work. It throws the following errors: How do I fix them?
Error:(19, 54) Cannot find JsonReader or JsonFormat type class for Array[A$A61.this.myTest]
lazy val converted= trainingDataRef.toJson.convertTo[Array[myTest]]
^
Error:(19, 54) not enough arguments for method convertTo: (implicit evidence$1: spray.json.JsonReader[Array[A$A61.this.myTest]])Array[A$A61.this.myTest].
Unspecified value parameter evidence$1.
lazy val converted= trainingDataRef.toJson.convertTo[Array[myTest]]
^
Error:(10, 61) could not find implicit value for evidence parameter of type spray.json.DefaultJsonProtocol.JF[Map[String,Any]]
implicit val format: RootJsonFormat[myTest] = jsonFormat3(myTest.apply)
^
Code: ^
import spray.json.DefaultJsonProtocol._
import spray.json._
case class myTest (
id: String,
classDetails: Map[String, Any],
school: Map[String, Any])
object myTest {
implicit val format: RootJsonFormat[myTest] = jsonFormat3(myTest.apply)
}
val trainingDataRef = """[{"id":"my-id","classDetails":{"sec":"2","teacher":"John"},"school":{"name":"newschool"}}]"""
println(trainingDataRef.getClass)
val converted= trainingDataRef.toJson.convertTo[Array[myTest]]
println(converted)
spray-json has good documentation, try take a look there. Basically, you have to define your case classes and implement JsonFormat for them:
import spray.json.DefaultJsonProtocol._
import spray.json._
case class ClassDetails(sec: String, teacher: String)
object ClassDetails {
implicit val format: RootJsonFormat[ClassDetails] = jsonFormat2(ClassDetails.apply)
}
case class School(name: String)
object School {
implicit val format: RootJsonFormat[School] = jsonFormat1(School.apply)
}
case class ClassInfo
(
id: String,
classDetails: ClassDetails,
school: School
)
object ClassInfo {
implicit object ClassInfoFormat extends RootJsonFormat[ClassInfo] {
def write(c: ClassInfo): JsValue = JsObject(
"id" -> JsString(c.id),
"classDetails" -> c.classDetails.toJson,
"school" -> c.school.toJson
)
def read(value: JsValue): ClassInfo = {
value.asJsObject.getFields("id", "classDetails", "school") match {
case Seq(JsString(name), details, school) =>
new ClassInfo(name, details.convertTo[ClassDetails], school.convertTo[School])
case _ => throw new DeserializationException("ClassInfo expected")
}
}
}
}
val json = """[{"id":"my-id","classDetails":{"sec":"2","teacher":"John"},"school":{"name":"newschool"}}]"""
// JSON string to case classes
val classInfos = json.parseJson.convertTo[Seq[ClassInfo]]
classInfos.zipWithIndex.foreach { case (c, idx) =>
println(s"$idx => $c")
}
println
// Seq[ClassInfo] to JSON
println(s"$classInfos: ")
println(classInfos.toJson.prettyPrint)

No instance of play.api.libs.json.Format is available for scala.Predef.Map[java.lang.String, scala.Option[scala.Double]]

Trying to write a json format for an entity which contains a Map of Option. It throws following error
Error:(8, 68) No instance of play.api.libs.json.Format is available for scala.Predef.Map[java.lang.String, scala.Option[scala.Double]] in the implicit scope (Hint: if declared in the same file, make sure it's declared before)
Code snippet:
import play.api.libs.json.{Json, OFormat}
val a: Map[String, Option[Double]] = Map("a" -> None)
case class Person(x: Map[String, Option[Double]])
object Person {
implicit val personFormat: OFormat[Person] = Json.format[Person]
}
Json.toJson(Person(a))
You can define implicits:
import scala.collection.Map
object Person {
implicit object MapReads extends Reads[Map[String, Option[Double]]] {
def reads(jsValue: JsValue): JsResult[Map[String, Option[Double]]] = jsValue match {
case JsObject(map) => JsSuccess(
map.mapValues {
case JsNumber(d) => Some(d.toDouble)
case _ => None
}
)
case _ => JsError()
}
}
implicit object MapWrites extends Writes[Map[String, Option[Double]]] {
def writes(map: Map[String, Option[Double]]): JsValue =
JsObject(map.mapValues(optd => JsNumber(optd.getOrElse[Double](0.0))))
}
implicit val personFormat: OFormat[Person] = Json.format[Person]
}
println(Json.toJson(Person(a)))//{"x":{"a":0}}
Based on answer.
The problem seems to be the inability of play-json macros to work with nested type variables:
Map[String, Option[Double]]
You could use an intermediate type wrapping Option
import play.api.libs.json.{Json, OFormat}
case class OptionDouble(value: Option[Double])
case class Person(x: Map[String, OptionDouble])
implicit val optionDoubleFormat = Json.format[OptionDouble]
implicit val personFormat = Json.format[Person]
val a: Map[String, OptionDouble] = Map("a" -> OptionDouble(None))
Json.toJson(Person(a))
Another option could be to write the formatter by hand.

Not able to parse Map with Enum to Json in Play Scala

We use Scala 2.11.8 and Play framework 2.5.8
Data to work with can be as simple as that:
object EnumA extends Enumeration {
type EnumA = Value
val ONE, TWO, THREE = Value
}
case class NoWork(data: Map[EnumA.Value, String] = Map.empty)
And what I want to archive is to be able to parse the NoWork class to Json. I know that to it requires providing an implicit formatter for Enumeration.
I have found this solution: https://stackoverflow.com/a/15489179/1549135 and applied it.
The companion object providing those implicits looks as follows:
object NoWork {
implicit val enumAFormat = EnumUtils.enumFormat(EnumA)
implicit val jsonModelFormat = Json.format[NoWork]
}
And it always fails with an error:
error: No implicit format for Map[EnumA.Value,String] available.
implicit val jsonModelFormat = Json.format[NoWork]
^
What is the issue?
I have tested and changing the data type to Map[String, String] allows for serialization. The Enum on its own is serializable too, so now - how to fix the Map with Enum type?
Thanks!
Edit
As Pamu's answer
implicit val writes = new Writes[Map[EnumA.Value, String]] {
override def writes(o: Map[EnumA.Value, String]): JsValue = Json.toJson(o.map { case (a, b) => Json.parse(s"""{${Json.toJson(a)}:${Json.toJson(b)}}""")}.toList)
}
would clearly work for this situation, I would actually need a generic solution for other Map[Enum, T] that I could use in whole application.
Note that it is mandatory for Json keys to be strings.
Following code works
Json.toJson(Map("mon" -> EnumA.MON))
Following code does not work because key for valid Json should always be string. Here the key is EnumA.Value which is not String.
scala> Json.toJson(Map(EnumA.MON -> "mon"))
<console>:19: error: No Json serializer found for type scala.collection.immutable.Map[EnumA.Value,String]. Try to implement an implicit Writes or Format for this type.
Json.toJson(Map(EnumA.MON -> "mon"))
But if you want it work as expected provide a writes
implicit val writes = new Writes[Map[EnumA.Value, String]] {
override def writes(o: Map[EnumA.Value, String]): JsValue = Json.toJson(o.map { case (a, b) => Json.parse(s"""{${Json.toJson(a)}:${Json.toJson(b)}}""")}.toList)
}
now following code works
Json.toJson(Map(EnumA.MON -> "hello"))
You can declare format for EnumA as following
object EnumA extends Enumeration {
val MON = Value("monday")
val TUE = Value("Tuesday")
implicit val format = new Format[EnumA.Value] {
override def writes(o: EnumA.Value): JsValue = Json.toJson(o.toString)
override def reads(json: JsValue): JsResult[EnumA.Value] = json.validate[String].map(EnumA.withName(_))
}
}
Scala REPL output
scala> object EnumA extends Enumeration {
| val MON = Value("monday")
| val TUE = Value("Tuesday")
|
| implicit val format = new Format[EnumA.Value] {
| override def writes(o: EnumA.Value): JsValue = Json.toJson(o.toString)
| override def reads(json: JsValue): JsResult[EnumA.Value] = json.validate[String].map(EnumA.withName(_))
| }
| }
defined object EnumA
scala> Json.toJson(EnumA.MON)
res0: play.api.libs.json.JsValue = "monday"
scala> (Json.parse("""{"a": "monday"}""") \ "a").validate[EnumA.Value]
res7: play.api.libs.json.JsResult[EnumA.Value] = JsSuccess(monday,)
scala> (Json.parse("""{"a": "monday"}""") \ "a").validate[EnumA.Value].get
res10: EnumA.Value = monday
scala> Json.toJson(Map("mon" -> EnumA.MON))
res2: play.api.libs.json.JsValue = {"mon":"monday"}
scala> Json.toJson(Map(EnumA.MON -> "mon"))
<console>:19: error: No Json serializer found for type scala.collection.immutable.Map[EnumA.Value,String]. Try to implement an implicit Writes or Format for this type.
Json.toJson(Map(EnumA.MON -> "mon"))
scala> implicit val writes = new Writes[Map[EnumA.Value, String]] {
| override def writes(o: Map[EnumA.Value, String]): JsValue = Json.toJson(o.map { case (a, b) => Json.parse(s"""{${Json.toJson(a)}:${Json.toJson(b)}}""")}.toList)
| }
writes: play.api.libs.json.Writes[Map[EnumA.Value,String]] = $anon$1#65aebb67
scala> Json.toJson(Map(EnumA.MON -> "hello"))
res2: play.api.libs.json.JsValue = [{"monday":"hello"}]
With colleague we have prepared a generic class that provides JSON serialization for Map[E <: Enum[E], T] type.
The Enum type is always converted to String as it is required for JsObject key. The other parameter is generic and is converted using the implicit format: Format[T]
import play.api.data.validation.ValidationError
import play.api.libs.json._
import scala.util.{Failure, Success, Try}
class MapEnumFormat[E <: Enum[E], T](valueOf: (String => E))(implicit format: Format[T]) extends Format[Map[E, T]] {
override def writes(o: Map[E, T]): JsValue = {
JsObject(o.map { case (a, b) => (a.name, Json.toJson(b)) })
}
override def reads(json: JsValue): JsResult[Map[E, T]] = {
val result = Try(json.as[Map[String, T]].map {
case (key, value) =>
valueOf(key) -> value
})
result match {
case Success(status) =>
JsSuccess(status)
case Failure(th) =>
JsError(ValidationError(s"Error while serializing $json: $th"))
}
}
}