json4s extract error in scala with map values - json

I use json4s native,with a json string like this
val myjson = """
{
"normative":"C",
"prefixType":{
"cod":["smallint", "int", "varchar(5)"],
"des":["varchar", "string"],
"fec":["timestamp"],
"hms":["timestamp"],
"tim":["timestamp"],
"imp":["decimal","Float", "Double"]
},
"fixcolname":{
"aud_usuario":"varchar(8)",
"aud_fec":"timestamp",
"aud_tim":"timestamp"
},
"symSep":"_",
"maxLength":26
}"""
And a case class
case class colVerify(prefixType: Map[String, Array[String]], fixcolname: Map[String, String], symSep: String, maxLength: Int)
and I want to extract it from the json String
val t = parse(myjson)
implicit val formats = DefaultFormats
val myvfy = t.extract[colVerify]
then got an error like this
Exception in thread "main" org.json4s.package$MappingException: Parsed JSON values do not match with class constructor
args=Map(des -> [Ljava.lang.String;#d7b1517, fec -> [Ljava.lang.String;#16c0663d, tim -> [Ljava.lang.String;#23223dd8, hms -> [Ljava.lang.String;#4ec6a292, imp -> [Ljava.lang.String;#1b40d5f0, cod -> [Ljava.lang.String;#ea4a92b),Map(aud_usuario -> varchar(8), aud_fec -> timestamp, aud_tim -> timestamp),_,26
arg types=scala.collection.immutable.HashMap$HashTrieMap,scala.collection.immutable.Map$Map3,java.lang.String,java.lang.Integer
constructor=public colVerify(scala.collection.mutable.Map,scala.collection.mutable.Map,java.lang.String,int)
Seems like it has problem with the type of Map, but how can I convert it implicitly?

The problem is that the maps in your case class are mutable maps, is this intentional or did you accidently import collection.mutable.Map?
If you really want the mutable maps, you could implement a custom Serializer as described here: https://github.com/json4s/json4s#serializing-non-supported-types
My first idea to add another constructor with immutable maps in case class doesn't seem to work reliably.

Related

How to know which field is missed while parsing json in Json4s

I have made a generic method which parses json to case class and it also works fine. But if tries to parse big json which have one or two mandatory field then I am not able to figure out which particular mandatory f ield is missing. I am only able to handle it with IllegalArgumentException. Is there a way to handle to know which is field is missing while parsing Json by using json4s.
Here is my code ->
object JsonHelper {
implicit val formats: DefaultFormats = DefaultFormats
def write[T <: AnyRef](value: T): String = jWrite(value)
def parse(value: String): JValue = jParser(value)
}
And this is the method I am using to parse Json and handle failed case ->
def parseJson[M](json: String)(implicit m: Manifest[M]): Either[ErrorResponse, M] = {
try
Right(JsonHelper.parse(json).extract[M])
catch {
case NonFatal(th) =>
th.getCause.getCause match {
case e: java.lang.IllegalArgumentException =>
error(s"Invalid JSON - $json", e)
Left(handle(exception = EmptyFieldException(e.getMessage.split(":").last)))
case _ =>
error(s"Invalid JSON - $json", th)
Left(handle(exception = new IllegalArgumentException("Invalid Json", th)))
}
}
}
Like for a Json ->
{
"name": "Json"
}
And case class ->
case class(name: String, profession: String)
if I try to parse above json into case class currently I am getting Invalid JSON - IllegalArgumentException. But is there a way that the exception tells which is field is missing like in above example "profession" is missing.
Is there a way to handle to know which is field is missing while parsing Json by using json4s.
Maybe you have more complicated setting, but for example for
import org.json4s._
import org.json4s.jackson.JsonMethods._
val str = """{
| "name": "Json"
|}""".stripMargin
val json = parse(str) // JObject(List((name,JString(Json))))
implicit val formats: Formats = DefaultFormats
case class MyClass(name: String, profession: String)
json.extract[MyClass]
it produces
org.json4s.MappingException: No usable value for profession
Did not find value which can be converted into java.lang.String
at org.json4s.reflect.package$.fail(package.scala:56)
at ...
with the name of missing field and if the class is just case class MyClass(name: String) then this produces MyClass(Json).
If the class is case class MyClass(name: String, profession: Option[String]) then this produces MyClass(Json,None).
So normally IllegalArgumentException should be followed by Caused by: org.json4s.MappingException with the field name. I guess now you're swallowing json4s MappingException somewhere. Maybe in th.getCause.getCause match .... It's hard to say without MCVE.

Scala get JSON value in a specific data type on map object

Using jackson library I read json data from a file (each row of file is a JSON object) an parse it to a map object of String and Any. My goal is to save specified keys (id and text) to a collection.
val input = scala.io.Source.fromFile("data.json").getLines()
val mapper = new ObjectMapper() with DefaultScalaModule
val data_collection = mutable.HashMap.empty[Int, String]
for (i <- input){
val parsedJson = mapper.readValue[Map[String, Any]](i)
data_collection.put(
parsedJson.get("id"),
parsedJson.get("text")
)
But as the values in the parsedJson map have the Any type, getting some keys like id and text, it returns Some(value) not just the value with the appropriate type. I expect the values for the id key to be Integer and values for the text to be String.
Running the code I got the error:
Error:(31, 23) type mismatch;
found : Option[Any]
required: Int
parsedJson.get("id"),
Here is a sample of JSON data in the file:
{"text": "Hello How are you", "id": 1}
Is it possible in Scala to parse id values to Int and text values to String, or at least convert Some(value) to value with type Int or String?
If you want to get a plain value from a Map instead of a Option you can use the () (apply) method - However it will throw an exception if the key is not found.
Second, Scala type system is static not dynamic, if you have an Any that's it, it won't change to Int or String at runtime, and the compiler will fail - Nevertheless, you can cast them using the asInstanceOf[T] method, but again if type can't be casted to the target type it will throw an exception.
Please note that even if you can make your code work with the above tricks, that code wouldn't be what you would expect in Scala. There are ways to make the code more typesafe (like pattern matching), but parsing a Json to a typesafe object is an old problem, I'm sure jackson provides a way to parse a json into case class that represent your data. If not take a look to circe it does.
Try the below code :
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import
com.fasterxml.jackson.module.scala.experimental.ScalaObjectMapper
val input = scala.io.Source.fromFile("data.json").getLines()
val mapper = new ObjectMapper() with ScalaObjectMapper
mapper.registerModule(DefaultScalaModule)
val obj = mapper.readValue[Map[String, Any]](input)
val data_collection = mutable.HashMap.empty[Int, String]
for (i <- c) {
data_collection.put(
obj.get("id").fold(0)(_.toString.toInt),
obj.get("text").fold("")(_.toString)
)
}
println(data_collection) // Map(1 -> Hello How are you)

Get case class and JSON object from a hierarchical JSON map with lift-json

I get maps such as:
Map(reference -> Map(scriptRenderings -> List(Map(text -> महा-सुभाषित-सङ्ग्रहे 9979, scheme -> null, startLetter -> म)), jsonClass -> QuoteText, metre -> None, key -> महा-सुभाषित-सङ्ग्रहे9979, language -> Map(code -> UNK)))
from my couchdb-lite database library.
I need to convert them into case classes I've defined. How do I accomplish this (preferably with lift-json, which I'm already using)?
And how to convert this map to a JSON object ? (Essentially reversing this.)
I ultimately ended up converting the map to string and parsing the string to the case class:
val jsonStr = Serialization.writePretty(jsonMap)
// log debug jsonStr
val quoteText = Serialization.read[QuoteText](jsonStr)

Error in json serialization in Scala with Play2

i have following three case classes
case class Delete(var deleteStatus : DeleteStatus , var deleteReason : DeleteReason) // DeleteStatus and DeleteReason are enums
case class Message(val uuid: Int ,val subject : String, val body : String, var awt : Int,val dateTime : LocalDateTime = LocalDateTime.now(), delete : Delete)
case class Inbox( val uuid : Int,var messageList : ListBuffer[Message] )
i want to serialize them to Json and but i am not sure how should i do this
i have tried it like this
def writedelete(delete: Delete) = Json.obj(
"deleteStatus" -> delete.getDeleteStatusInt.toString,
"deleteReason" -> delete.getDeleteReasonInt.toString
)
def writeMessage(mgs : Message)= Json.obj(
"uuid" -> mgs.getUuid ,
"subject" -> mgs.getSubject,
"body" -> mgs.getBody,
"awt" -> mgs.getAwt,
"datetime" -> mgs.getdateTime.toString,
"delete" -> mgs.delete
)
def writeInbox(inbox : Inbox)= Json.obj(
"uuid" -> inbox.getUuid,
"mgslist" -> Seq(inbox.getMessageList)
)
but it gives following error on mgs.delete in writeMessage and mgslist in writeInbox
type mismatch; found : models.UserNotifications.MailMessages.Delete
required: play.api.libs.json.Json.JsValueWrapper
type mismatch; found :
Seq[scala.collection.mutable.ListBuffer[models.UserNotifications.MailMessages.Message]]
required: play.api.libs.json.Json.JsValueWrapper
please guide me how can i get get rid off it
and also is there any better way of doing this?
When you use Json.obj(...) to construct a JSON object various implicit conversions are available to convert common types (e.g. String, Int) to their JSON wrapper types (JsString, JsNumber). The problem with your code is there are no implicit conversions available to convert your Delete and Message types to JSON. One option would be to use your explicit conversion functions directly, e.g:
"delete" -> writeDelete(mgs.delete)
and (using Json.arr(...) to construct a JSON array):
"msglist" -> Json.arr(inbox.getMessageList.toSeq.map(writeMessage): _*)
However, the more idiomatic way to do this would be to use the JSON Inception macros to automatically generate (implicit) serializers for your types.
Simplifying slightly, this would look something like this:
case class Delete(deleteStatus: DeleteStatus, deleteReason: DeleteReason)
object Delete {
implicit val _format = Json.format[Delete]
}
case class Message(uuid: Int, subject: String, body: String, awt: Int, dateTime: LocalDateTime, delete: Delete)
object Message {
implicit val _format = Json.format[Message]
}
case class Inbox(uuid: Int, messageList: ListBuffer[Message])
object Inbox {
implicit val _format = Json.format[Message]
}
You should now be able to automatically serialize (and de-serialize) your Delete, Message, and Inbox objects using Json.toJson(thing) because it will find an implicit Format object (a combined Reads and Writes) on the companion object of each custom type.
One complication here is that your case classes contain enums; if they're Scala enumerations see this answer for how to convert them. I leave that as an exercise for the reader.

Play framework Json output issue

I have a simple action which outputs a json object string, like this:
Ok(toJson(Map(
"results" -> result_lists
)))
This works all right. But if I do:
Ok(toJson(Map(
"action" -> action_string, // a Scala String
"results" -> result_lists // a Scala List
)))
I got
No Json serializer found for type scala.collection.immutable.Map[String,java.io.Serializable]
compilation error...what's the problem?
As others have posted in the comments before, the type of the Map is not something which can be deserialized into Json by the framework, but you can easily get rid of the Map:
scala> val s = "hello"
s: String = hello
scala> val list = List(1,2,3)
list: List[Int] = List(1, 2, 3)
scala> Json.obj("somestring" -> s, "somemap" -> list)
res0: play.api.libs.json.JsObject = {"somestring":"hello","somemap":[1,2,3]}
The resulting object can then be returned by the action as desired.