spray.json.DeserializationException: Expected String as JsString, but got {} - scala

I am using Either implementation for my object because I am expecting an empty Json for one of the parameter in the object. Here is the object:
case class Record(id: String, version: Long, payload: Either[PayloadObject, String]))
I am trying to unit test this sending an empty json string which is like this:
val jsonString = """
| {
| "id":"someId"
| "version":123456
| "payload":{}
| }
|""".stripMargin
This is my unit test where I am deserializing the above json String:
{
val deserialized = Record("someId", 123456L, Right(""))
val result = jsonString.convertTo[Record]
result must equal(deserialized)
}
This is throwing error. spray.json.DeserializationException: Expected String as JsString, but got {}. How to represent the variable serialized as an empty JsString to run the unit tests? Thanks

If I understand your question correctly, you need to define a custom format for your case class.
case class Record(id: String, version: Long, payload: Either[JsObject, String])
object Record {
implicit val recordFormat: RootJsonReader[Record] = (json: JsValue) =>
json.asJsObject.getFields("id", "version", "payload") match {
case Seq(JsString(id), JsNumber(version), JsObject.empty) => Record(id, version.toLong, Right(""))
case Seq(JsString(id), JsNumber(version), payload: JsObject) => Record(id, version.toLong, Left(payload))
case _ => throw new Exception("Invalid record")
}
}

Related

Comparing the json data types at runtime using Jackson and Scala

I have an incoming JSON data that looks like below:
{"id":"1000","premium":29999,"eventTime":"2021-12-22 00:00:00"}
Now, I have created a class that will accept this record and will check whether the data type of the incoming record is according to the data types defined in the case class. However, when I am calling the method it is always calling the Failure part of the match case.
case class Premium(id: String, premium: Long, eventTime: String)
class Splitter extends ProcessFunction[String, Premium] {
val outputTag = new OutputTag[String]("failed")
def fromJson[T](json: String)(implicit m: Manifest[T]): Either[String, T] = {
Try {
println("inside")
lazy val mapper = new ObjectMapper() with ScalaObjectMapper
mapper.registerModule(DefaultScalaModule)
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
mapper.readValue[T](json)
} match {
case Success(x) => {
Right(x)
}
case Failure(err) => {
Left(json)
}
}
}
override def processElement(i: String, context: ProcessFunction[String, Premium]#Context, collector: Collector[Premium]): Unit = {
fromJson(i) match {
case Right(data) => {
collector.collect(data)
println("Good Records: " + data)
}
case Left(json) => {
context.output(outputTag, json)
println("Bad Records: " + json)
}
}
}
}
Based on the sample record above, it should pass the Success value but no matter what I pass, it always enters the Failure part. What else is missing?
I am using Scala 2.11.12 and I tried examples from this link and this link but no luck.

Play Json Validation is failing on Date field with Error: [error.expected.date.isoformat]

I have a JsonValidationError.
This is my model:
case class Account(id: Long, name: String, birthDate: LocalDate)
object Account {
implicit val accountFormat = Json.format[Account]
}
And this is part of my controller action:
def updateProfile = Action.async(parse.json) { implicit request =>
val res = request.body.validate[Account]
logger.info(request.body.toString)
logger.info(res.toString)
...
}
I have this output:
[info] c.a.AccountController - {"id":1,"name":"John","birthDate":"1983-02-11T23:00:00.000Z"}
[info] c.a.AccountController - JsError(List((/birthDate,List(JsonValidationError(List(error.expected.date.isoformat),ArraySeq(ParseCaseSensitive(false)(Value(Year,4,10,EXCEEDS_PAD)'-'Value(MonthOfYear,2)'-'Value(DayOfMonth,2))[Offset(+HH:MM:ss,'Z')])))))
The data coming from the client seems to be valid, I don't understand that Error, I think the birthDate is in the isoFormat, so what can be the issue here ?
Please help.
The input has a different format than expected for java.time.LocalDate.
Try the following custom codec to make the issue gone:
case class Account(id: Long, name: String, birthDate: LocalDate)
object Account {
implicit val accountFormat: OFormat[Account] = {
implicit val customLocalDateFormat: Format[LocalDate] = Format(
Reads(js => JsSuccess(Instant.parse(js.as[String]).atZone(ZoneOffset.UTC).toLocalDate)),
Writes(d => JsString(d.atTime(LocalTime.of(23, 0)).atZone(ZoneOffset.UTC).toInstant.toString)))
Json.format[Account]
}
}
val json = """{"id":1,"name":"John","birthDate":"1983-02-11T23:00:00.000Z"}"""
val account = Json.parse(json).as[Account]
println(account)
println(Json.toJson(account))
An expected output of this code snippet is:
Account(1,John,1983-02-11)
{"id":1,"name":"John","birthDate":"1983-02-11T23:00:00Z"}

How to dynamically select class type when parsing JSON to object in Play Framework?

The following sample code uses Play Framework to parse JSON to an object:
def createEvent(): Action[JsValue] = Action.async(parse.tolerantJson) {
request => {
request.body.validate[SomeEvent] match {
case o:JsSuccess[SomeEvent] => {
//do something
Future(Created)
}
}
}
}
Is it possible to generalise it so it can handle different event types? e.g.
def createEvent(): Action[JsValue] = Action.async(parse.tolerantJson) {
request => {
val eventType = request.contentType match {
case Some("SomeEventType") => SomeEvent
case Some("OtherEventType") => OtherEvent
}
request.body.validate[eventType] match {
case o:JsSuccess[eventType] => {
//do something
Future(Created)
}
}
}
}
Currently, the above code will fail in the line request.body.validate[eventType]
You can extract body.validate[T] into a function and call it from your patten matching construct with a proper type, i.e:
def extract[T: JsonFormat](implicit req: Request[AnyContent]) = req.body.valudate[T]
request.contentType match {
case Some("SomeEventType") => extract[SomeEvent]
case Some("OtherEventType") => extract[OtherEvent]
}
You can read and create class from contentType dynamically. But you can have problem, if will be no implicit Format for extracted type in scope:
error: No Json formatter found for type A. Try to implement an implicit Format for this type.
or
java.lang.ClassNotFoundException: models.Bazz
The available data:
model
package models
case class Bar(name: String)
request
request.contentType: Option[String] = Some("models.Bar")
incoming body
request.body: JsValue = """{"name": "bar name"}"""
format[Bar]
implicit val BarFormat = Json.format[models.Bar]
extractor:
def extract[A <: Any](ct: String, json: JsValue)(implicit format: Format[A]) = {
val manifest:Manifest[A] = Manifest.classType[A](Class.forName(ct))
json.validate[A]
}
request.contentType.map(extract(_, request.body))
res1: Option[play.api.libs.json.JsResult[models.Bar]] = Some(JsSuccess(Bar(bar name),/name))
you can see https://github.com/strongtyped/fun-cqrs/blob/demos/shop-sample/samples/shop/src/main/scala/funcqrs/json/TypedJson.scala for other way of deserializing self-contained json.

Escape characters in a dynamic List

I'll like to escape characters in a dynamic List used in creating a case class.
case class Profile(biography: String,
userid: String,
creationdate: String) extends Serializable
object Profile {
val cs = this.getClass.getConstructors
def createFromList(params: List[Any]) = params match {
case List(biography: Any,
userid: Any,
creationdate: Any) => Profile(StringEscapeUtils.escapeJava(biography.asInstanceOf[String]),
StringEscapeUtils.escapeJava(creationdate.asInstanceOf[String]),
StringEscapeUtils.escapeJava(userid.asInstanceOf[String]))
}
}
JSON.parseFull("""{"biography":"An avid learner","userid":"165774c2-a0e7-4a24-8f79-0f52bf3e2cda", "creationdate":"2015-07-13T07:48:47.924Z"}""")
.map(_.get.asInstanceOf[scala.collection.immutable.Map[String, Any]])
.map {
m => Profile.createFromList(m.values.to[collection.immutable.List])
} saveToCassandra("testks", "profiles", SomeColumns("biography", "userid", "creationdate"))
I get this error:
scala.MatchError: List(An avid learner, 165774c2-a0e7-4a24-8f79-0f52bf3e2cda, 2015-07-13T07:48:47.925Z) (of class scala.collection.immutable.$colon$colon)
Any ideas please?
It might be simpler to use a different (external) JSON library than scala.util.parsing.json (which has been deprecated since Scala 2.11).
There are a lot of good Scala Json libraries, below an example using json4s.
import org.json4s._
import org.json4s.native.JsonMethods._
case class Profile(biography: String, userid: String, creationdate: String)
val json = """{
| "biography":"An avid learner",
| "userid":"165774c2-a0e7-4a24-8f79-0f52bf3e2cda",
| "creationdate":"2015-07-13T07:48:47.924Z"
|}""".stripMargin
parse(json).extract[Profile]
// Profile(An avid learner,165774c2-a0e7-4a24-8f79-0f52bf3e2cda,2015-07-13T07:48:47.924Z)

required: spray.httpx.marshalling.ToResponseMarshallable Error

Hey im pretty new to Spray and reactive mongo .
Im trying to return a list of result as json but i'm having some issue with converting the result to list of json.
this is my model
import reactivemongo.bson.BSONDocumentReader
import reactivemongo.bson.BSONObjectID
import reactivemongo.bson.Macros
case class Post(id: BSONObjectID, likes: Long, message: String, object_id: String, shares: Long)
object Post {
implicit val reader: BSONDocumentReader[Post] = Macros.reader[Post]
}
the Mongo method
def getAll(): Future[List[Post]] ={
val query = BSONDocument(
"likes" -> BSONDocument(
"$gt" -> 27))
collection.find(query).cursor[Post].collect[List]()
}
and this is the route
val route1 =
path("posts") {
val res: Future[List[Post]]= mongoService.getAll()
onComplete(res) {
case Success(value) => complete(value)
case Failure(ex) => complete(ex.getMessage)
}
}
error
type mismatch; found : List[com.example.model.Post] required: spray.httpx.marshalling.ToResponseMarshallable
thanks,
miki
You'll need to define how a Post will be serialized, which you can do via a spray-json Protocol (see the docs for more detailed information). It's quite easy to do so, but before that, you'll also need to define a format for the BSONObjectId type, since there's no built-in support for that type in spray-json (alternatively, if object_id is a string representation of the BSONObjectId, think about removing the id property from your Post class or change it to be a String):
// necessary imports
import spray.json._
import spray.httpx.SprayJsonSupport._
implicit object BSONObjectIdProtocol extends RootJsonFormat[BSONObjectID] {
override def write(obj: BSONObjectID): JsValue = JsString(obj.stringify)
override def read(json: JsValue): BSONObjectID = json match {
case JsString(id) => BSONObjectID.parse(id) match {
case Success(validId) => validId
case _ => deserializationError("Invalid BSON Object Id")
}
case _ => deserializationError("BSON Object Id expected")
}
}
Now, we're able to define the actual protocol for the Post class:
object PostJsonProtocol extends DefaultJsonProtocol {
implicit val format = jsonFormat5(Post.apply)
}
Furthermore, we'll also need to make sure that we have the defined format in scope:
import PostJsonProtocol._
Now, everything will compile as expected.
One more thing: have a look at the docs about the DSL structure of spray. Your mongoService.getAll() isn't within a complete block, which might not reflect your intentions. This ain't an issue yet, but probably will be if your route get more complex. To fix this issue simply put the future into the onComplete call or make it lazy:
val route1 =
path("posts") {
onComplete(mongoService.getAll()) {
case Success(value) => complete(value)
case Failure(ex) => complete(ex.getMessage)
}
}