Kotlin - parsing json string throws MalformedJsonException: Unterminated object - json

I am trying to save a value to a jsonb DB column:
val deliveryAddressAsJson = deliveryAddress?.toJson()
val lat = deliveryAddressAsJson?.get("latitude")
val lng = deliveryAddressAsJson?.get("longitude")
val dataJson = jsonObject("comment" to "KARTKOORD:#LE#$lat#$lng# #")
val values = mapOf(
"type" to EventType.RESOLVED.dbName,
"created_by" to ctx.userId,
"data" to dataJson.toPgObject(),
"package_id" to packageId
)
#Language("PostgreSQL")
val sql = """insert into package_event(type, created_by, data, package_id) values (:type, :created_by, :data, :package_id)""".trimMargin()
insert(ctx, sql, values).bind()
I can see that the data is saved like this:
data -> {Collections$SingletonMap#6348} size = 1
key = "data"
value = {Collections$SingletonMap#6348} size = 1
key = "comment"
value = "KARTKOORD:#LE#59.8098962#10.7809297# #"
But, if I try to parse it:
val resolvedPackageEvent = fetchRows(ctx, queryOf("select * from package_event where package_id = ? and type = 'resolved'", packageId)).first()
val data = parseJson(resolvedPackageEvent.string("data"))
val deliveryAddress = data.get("comment")
I get an exception thrown:
com.google.gson.JsonSyntaxException: com.google.gson.stream.MalformedJsonException: Unterminated object at line 1 column 20 path $.comment
If I try to get the value with jsonObject method like this:
fun Entity.jsonObject(key: String): JsonObject = when (val v = this[key]) {
is String -> parseJson(v)
else -> v as JsonObject
}
resolvedPackageEvent.jsonObject("data")
I get an exception:
java.lang.ClassCastException: class java.util.Collections$SingletonMap cannot be cast to class com.google.gson.JsonObject (java.util.Collections$SingletonMap is in module java.base of loader 'bootstrap'; com.google.gson.JsonObject is in unnamed module of loader 'app')
How should I parse this json string?

Your data is not an String. As you can see in the debugger, your data is something like
{
"data": {
"comment": "KARTKOORD:#LE#59.8098962#10.7809297# #"
}
}
I don't have the gson syntax at hand, so I can't provide precise code for retrieving the value. You can probably do something like
val result: JsonObject = parseJson(resolvedConsignmentEvent)
val data: JsonObject = result.get("data")
val deliveryAddress: String = data.get("comment")

Related

JSONException Error upon parsing an array of values that I intend to handle in a RecyclerView

I am trying to parse a JSONArray using Volley and put it in a RecyclerView. I am stuck on this problem for days now and I really need some help. Everything seems to be fine but it just gives me a FATAL Exception regarding a JSONException. I have also used Postman and the Data Request seems to be fine. I just think there is a problem in what I am doing in my Kotlin code. Here is my code:
Get Data Function
val getLoanRecData = BASE_URL + "getLoanData"
val queue = Volley.newRequestQueue(this.activity)
val loanAppRequest = object : StringRequest(Method.POST,getLoanRecData,
Response.Listener { response ->
val jsonObject = JSONObject(response)
if(jsonObject.get("response").equals("Success"))
{
val jsonArray = jsonObject.getJSONArray("data")
for (i in 0..jsonArray.length()-1){
var jo = jsonArray.getJSONObject(i)
val id = jo.get("col_borrower_id").toString()
val loan_id = jo.get("col_loan_assignid").toString()
val loan_type = jo.get("col_loan_type").toString()
val due_date = jo.get("col_due_date").toString()
val loan_status = jo.get("col_status").toString()
val user = LoanRecordModel(id, loan_id, loan_type, loan_status, due_date)
list.add(user)
}
}else{
Toast.makeText(activity,jsonObject.get("response").toString(),Toast.LENGTH_SHORT).show()
}
}, Response.ErrorListener
{
error -> Toast.makeText(activity, error.toString(), Toast.LENGTH_SHORT).show()
}){
override fun getParams(): HashMap<String, String>
{
val sp = activity?.getSharedPreferences("user_data", Context.MODE_PRIVATE)
val emp_id = sp?.getString("user_id", "")
val map = HashMap<String,String>()
map["request"] = "SENT"
map["emp_id"] = emp_id.toString()
return map
}
}
queue.add(loanAppRequest)
I have also used serialized name on the data class for the RecyclerView because I thought the program only has problems regarding obtaining the data gathered from JSON, however that solved nothing and I still got the same error.
data class LoanRecordModel(
#SerialName("col_borrower_id")
val emp_id: String,
#SerialName("col_loan_assignid")
val loan_id: String,
#SerialName("col_loan_type")
val loan_type: String,
#SerialName("col_due_date")
val due_date: String,
#SerialName("col_status")
val loan_status: String)
These are the errors im getting:

How do I de-serialize this json?

I am working on a project where I need to access currency rates once a day so I am trying to use this json.
To read this, I am simply getting the text of the URL and then trying to use the JSONReader to de-serialize.
val url = URL("https://www.floatrates.com/daily/usd.json")
val stream = url.openStream()
url.readText()
val jsonReader = JsonReader(InputStreamReader(stream))
jsonReader.isLenient = true
jsonReader.beginObject()
while (jsonReader.hasNext()) {
val codeName:String = jsonReader.nextName()
jsonReader.beginObject();
var code:String? = null
var rate = 0.0
while (jsonReader.hasNext()) {
val name:String = jsonReader.nextName()
when(name){
"code" -> {
code = jsonReader.nextString()
break
}
"rate" -> {
rate = jsonReader.nextDouble()
break
}
else -> {
jsonReader.skipValue()
}
}
code?.let {
rates?.set(it, rate)
}
}
}
jsonReader.endObject();
When I run the code , I get:
Expected BEGIN_OBJECT but was STRING
at
jsonReader.beginObject();
When I try using Gson, with the code below:
var url = URL("https://www.floatrates.com/daily/usd.json").readText()
//url = "[${url.substring(1, url.length - 1)}]"
val gson = Gson()
val currencies:Array<SpesaCurrency> = gson.fromJson(url, Array<SpesaCurrency>::class.java)
I get this error :
Expected BEGIN_OBJECT but was STRING at line 1 column 3 path $[0]
at:
gson.fromJson(url, Array<SpesaCurrency>::class.java)
SpesaCurrency.kt looks like this:
class SpesaCurrency(
val code:String,
val alphaCode:String,
val numericCode:String,
val name:String,
val rate:Float,
val date:String,
val inverseRate:Float
)
Any help is greatly appreciated.
I think there is one
jsonReader.endObject();
missing in your code. That imbalance causes the program to fail after the first object has been read. Add it after the inner
while (jsonReader.hasNext()) {
...
}
loop.

How to convert Scala Document to JSON in Scala

I want to convert variable message which is of type scala.Seq[Scala.Document] to JSON format in following code:
path("getMessages"){
get {
parameters('roomname.as[String]) {
(roomname) =>
try {
val messagesByGroupName = MongoDatabase.collectionForChat.find(equal("groupChatName",roomname)).toFuture()
val messages = Await.result(messagesByGroupName,60.seconds)
println("Messages:"+messages)
complete(messages)
}
catch {
case e:TimeoutException =>
complete("Reading file timeout.")
}
}
}
But it is giving me error on complete(messages) line. It is not accepting message of that type.
I tried to convert it into JSON by using following :
import play.api.libs.json._
object MyJsonProtocol{
implicit object ChatFormat extends Format[Chat] {
def writes(c: Chat) : JsValue = {
val chatSeq = Seq (
"sender" -> JsString(c.sender),
"receiver" -> JsString(c.receiver),
"message" -> JsString(c.message),
"groupChatName" -> JsString(c.groupChatName),
)
JsObject(chatSeq)
}
def reads(value: JsValue) = {
JsSuccess(Chat("","","",""))
}
}
}
But it is not working.
My Chat.scala class is as follows:
import play.api.libs.json.{Json, Reads, Writes}
class Chat(var sender:String,var receiver:String,var message:String, var groupChatName:String){
def setSenderName(senderName:String) = {
sender = senderName
}
def setReceiverName(receiverName:String) = {
receiver = receiverName
}
def setMessage(getMessage:String) = {
message = getMessage
}
def setGroupChatName(chatName:String) = {
groupChatName = chatName
}
}
object Chat {
def apply(sender: String, receiver: String, message: String, groupname: String): Chat
= new Chat(sender, receiver, message,groupname)
def unapply(arg: Chat): Option[(String, String, String,String)] = ???
implicit val requestReads: Reads[Chat] = Json.reads[Chat]
implicit val requestWrites: Writes[Chat] = Json.writes[Chat]
}
I am also not able to figure out what to write in unapply method.
I am new to scala and akka.
EDIT:
My MongoDatabase.scala which has collection is as follows:
object MongoDatabase {
val chatCodecProvider = Macros.createCodecProvider[Chat]()
val codecRegistry = CodecRegistries.fromRegistries(
CodecRegistries.fromProviders(chatCodecProvider),
DEFAULT_CODEC_REGISTRY
)
implicit val system = ActorSystem("Scala_jwt-App")
implicit val executor: ExecutionContext = system.dispatcher
val mongoClient: MongoClient = MongoClient()
val databaseName = sys.env("database_name")
// Getting mongodb database
val database: MongoDatabase = mongoClient.getDatabase(databaseName).withCodecRegistry(codecRegistry)
val registrationCollection = sys.env("register_collection_name")
val chatCollection = sys.env("chat_collection")
// Getting mongodb collection
val collectionForUserRegistration: MongoCollection[Document] = database.getCollection(registrationCollection)
collectionForUserRegistration.drop()
val collectionForChat: MongoCollection[Document] = database.getCollection(chatCollection)
collectionForChat.drop()
}
And if try to change val collectionForChat: MongoCollection[Document] = database.getCollection(chatCollection)
to
val collectionForChat: MongoCollection[Chat] = database.getCollection[Chat](chatCollection)
then I get error on in saveChatMessage() method below:
def saveChatMessage(sendMessageRequest: Chat) : String = {
val senderToReceiverMessage : Document = Document(
"sender" -> sendMessageRequest.sender,
"receiver" -> sendMessageRequest.receiver,
"message" -> sendMessageRequest.message,
"groupChatName" -> sendMessageRequest.groupChatName)
val chatAddedFuture = MongoDatabase.collectionForChat.insertOne(senderToReceiverMessage).toFuture()
Await.result(chatAddedFuture,60.seconds)
"Message sent"
}
on val chatAddedFuture = MongoDatabase.collectionForChat.insertOne(senderToReceiverMessage).toFuture() this line since it accepts data of type Seq[Document] and I am trying to add data of type Seq[Chat]
I am going to assume that MongoDatabase.collectionForChat.find(equal("groupChatName",roomname)) returns either Seq[Chat], or Chat. Both of them are the same for play.
You have 2 options:
Adding the default format on the companion object:
object Chat {
implicit val format: Format[Chat] = Json.format[Chat]
}
In this case you can delete the object MyJsonProtocol which is not used.
In case you want to keep your own serializers(i.e. MyJsonProtocol), you need to rename MyJsonProtocol into Chat. This way the complete route will be able to find the implicit Format.
create case class for the message object you want to send
for example:
case class MyMessage(sender: String, receiver: String, message: String, groupChatName: String)
You should create Format for the type of case class
implicit val MessageTypeFormat = Json.format[MyMessage]
if complete should get JSON type - then call complete myMessage when myMessage is an instance of MyMessage.
complete(Json.toJson(myMessage))

Modify json field type via circe

I have simple Json:
val str = """{"test":"123"}"""
How I can modify String "123" to Int 123 to get new Json?:
{"test":123}
Now I am using:
val json = parse(str).getOrElse(Json.Null)
val jsObj = json.asObject.get // Unsafe, just example
val newJson = Json.fromJsonObject(jsObj.remove("test").add("test", Json.fromInt(123)))
But this code is not pretty.
Is it possible to make this code prettier or maybe do it via circe optics?
It should do the trick depending on how you want to manage the limit case (here I throw an exception):
import io.circe._
import io.circe.parser.parse
val str = """{"test":"123"}"""
val json = parse(str).getOrElse(Json.Null)
json.mapObject(
_.mapValues( v =>
v.asString
.flatMap(parse(_).toOption)
.getOrElse(throw new IllegalArgumentException("No String found"))
)
)

Appending values in a list and then sending as a JSON object

var jsonElements = List[String]()
val params = Map("host"->host)
for((key,value)<-mapSql){
val map = Map("metric"->key,"timestamp"->new Date().getTime,"value"->value,"tags"->params)
jsonElements=JsonUtility.toJSONString(map) :: jsonElements
}
val entity = new StringEntity(JsonUtility.toJSONString(jsonElements))
println("json elements final list is "+jsonElements)
println("json elements final JSON Obj is "+JsonUtility.toJSONString(jsonElements))
entity.setContentType(new BasicHeader("Content-Type", "application/json"))
val postRequest: HttpPost = new HttpPost(putURL)
postRequest.setEntity(entity)
val postResponse: CloseableHttpResponse = httpclient.execute(postRequest)
I basically need to add values to a list and then send them together in a JSON Array.
However this is introducing unnecessary escape characters "/" in the output which is rendering the post request useless and I am getting an error to the API hit. the following is the response :
json elements final list is List({"metric":"replicationLag","timestamp":1410179907871,"value":0.0,"tags":{"host":"tg-em-db01.nm.xxxx.com"}}, {"metric":"status","timestamp":1410179907824,"value":1,"tags":{"host":"tg-em-db01.nm.xxxxx.com"}})
json elements final JSON Obj is ["{\"metric\":\"replicationLag\",\"timestamp\":1410179907871,\"value\":0.0,\"tags\":{\"host\":\"tg-em-db01.nm.xxxx.com\"}}","{\"metric\":\"status\",\"timestamp\":1410179907824,\"value\":1,\"tags\":{\"host\":\"tg-em-db01.nm.xxxxx.com\"}}"]
I can replace and remove all the escape characters by the replaceAll function but I do not want to do that. is there a better way to append objects to an already existing JSON object and then change it to an array ( which i can easily do by new JsonArray(List(JsonObj)) ) so that i dont get any escape characters anywhere.
Something like this :
val params = Map("host"->host)
var map = Map[String,Any]()
for((key,value)<-mapSql){
map ++= Map("metric"->key,"timestamp"->new Date().getTime,"value"->value,"tags"->params)
}
val entity = new StringEntity(JsonUtility.toJSONString(List(map)))
println("json elements final list is "+map)
println("json elements final JSON Obj is "+JsonUtility.toJSONString(List(map)))
is giving me this as an ouput :
json elements final list is Map(metric -> replicationLag, timestamp -> 1410180939983, value -> 0.0, tags -> Map(host -> tg-em-db01.nm.xxxx.com))
json elements final JSON Obj is [{"metric":"replicationLag","timestamp":1410180939983,"value":0.0,"tags":{"host":"tg-em-db01.nm.xxxxx.com"}}]
But I need something like this :
[ {"metric":blah blah} , {"metric":blah blah} ]
Is there a way to append to maps such that the same key values are not clubbed ?
Thanks in advancE!
var jsonElements = List[Map[String, Any]]()
val params = Map("host" -> host)
for ((key, value) <- mapSql) {
val map = Map("metric" -> key, "timestamp" -> new Date().getTime, "value" -> value, "tags" -> params)
jsonElements = map :: jsonElements
}
val entity = new StringEntity(JsonUtility.toJSONString(jsonElements))
entity.setContentType(new BasicHeader("Content-Type", "application/json"))
val postRequest: HttpPost = new HttpPost(putURL)
postRequest.setEntity(entity)
val postResponse: CloseableHttpResponse = httpclient.execute(postRequest)
object JsonUtility {
def toJSONString(obj:Any):String = {
compact(JsonAST.render(decompose(obj)))
}
}