Can't parse json in Kotlin - json

I have a json like this:
{"ok":true,"result":[{"update_id":853803195,
"message":{"message_id":313,"from":{"id":104906563,"is_bot":false,"first_name":"AL","username":"alzvaracc","language_code":"en"},"chat":{"id":104906563,"first_name":"AL","username":"alzvaracc","type":"private"},"date":1594723984,"text":"/start","entities":[{"offset":0,"length":6,"type":"bot_command"}]}},{"update_id":853803196,
"message":{"message_id":314,"from":{"id":104906563,"is_bot":false,"first_name":"AL","username":"alzvaracc","language_code":"en"},"chat":{"id":104906563,"first_name":"AL","username":"alzvaracc","type":"private"},"date":1594723986,"text":"e"}},{"update_id":853803197,
"message":{"message_id":325,"from":{"id":104906563,"is_bot":false,"first_name":"AL","username":"alzvaracc","language_code":"en"},"chat":{"id":104906563,"first_name":"AL","username":"alzvaracc","type":"private"},"date":1594734252,"text":"\ud83d\ude06"}},{"update_id":853803198,
"message":{"message_id":328,"from":{"id":104906563,"is_bot":false,"first_name":"AL","username":"alzvaracc","language_code":"en"},"chat":{"id":104906563,"first_name":"AL","username":"alzvaracc","type":"private"},"date":1594736358,"text":"5"}}]}
I'm using klaxon library.
Like in the first example I created a class:
class Response(val ok: Boolean, val result: String)
I was trying to save the second parameter to a string, so I could parse it, too, later. But I get a exception like this:
Unable to instantiate Response with parameters [ok: true, result: [, , , ]]
I tried making result a JsonObject (or a JsonArray of JsonObjects) and got this
Unable to instantiate JsonObject with parameters []
The only thing that more or less worked was a List<Any>. Result becomes a list of java objects like this:
[java.lang.Object#680362a, java.lang.Object#3569edd5, java.lang.Object#1f651cd8, java.lang.Object#7d0332e1]
But I don't know how to deal with them. So my question is what do I do? How do I get the result I will be able to work with?

Try this class
import com.beust.klaxon.*
private val klaxon = Klaxon()
data class User (
val ok: Boolean,
val result: List<Result>
) {
public fun toJson() = klaxon.toJsonString(this)
companion object {
public fun fromJson(json: String) = klaxon.parse<User>(json)
}
}
data class Result (
#Json(name = "update_id")
val updateID: Long,
val message: Message
)
data class Message (
#Json(name = "message_id")
val messageID: Long,
val from: From,
val chat: Chat,
val date: Long,
val text: String,
val entities: List<Entity>? = null
)
data class Chat (
val id: Long,
#Json(name = "first_name")
val firstName: String,
val username: String,
val type: String
)
data class Entity (
val offset: Long,
val length: Long,
val type: String
)
data class From (
val id: Long,
#Json(name = "is_bot")
val isBot: Boolean,
#Json(name = "first_name")
val firstName: String,
val username: String,
#Json(name = "language_code")
val languageCode: String
)

Related

Jackson JSON Serialization without field name from one string row

I have this JSON to deserialize:
"data": {
"type": 18,
"msg": "firstName,lastName,15"
"timestamp": 1551770400000
}
I want to get this data in my model:
class DataDto(
type: String,
timestamp: Long,
msg: DataMsgDto?
) {
#JsonFormat(shape = JsonFormat.Shape.STRING)
#JsonPropertyOrder("firstName", "lastName", "age")
class DataMsgDto(
firstName: String,
lastName: String,
age: Long
)
}
I use this code to get data:
DataBuffer payload //this is what I get from server
val jsonNode = objectMapper.readTree(payload.toString(StandardCharsets.UTF_8))
objectMapper.treeToValue(jsonNode, DataDto::class.java)
But this does not work because in msg I don't have fields. So, how can I do this?
You have a JSON property that contains comma-separated values. The JSON message is valid, and can be deserialized by Jackson.
It's possible to write custom Jackson code that can handle this case automatically
How to deserialize a string separated by comma to list with Jackson commonly?
Jackson json property (string) to instance
But the most practical way to solve this is unlikely to be digging around trying to make Jackson understand msg, or writing a custom deserializer. Instead, allow msg to be a String
class DataDto(
val type: String,
val timestamp: Long,
// just use a normal String for msg
val msg: String?
)
And then manually decode it in Kotlin
fun parseDataDtoMsg(dto: DataDto): DataMsgDto? {
val msgElements = dto.msg?.split(",") ?: return null
// parse each message - you can chose whether to
// throw an exception if the `msg` does not match what you expect,
// or ignore invalid data and return `null`
val firstName = ...
val lastName = ...
val age = ...
return DataDtoMsg(
firstName = firstName,
lastName = lastName,
age = age,
)
}
data class DataMsgDto(
val firstName: String
val lastName: String
val age: Long,
)

Convert JsonFactory object into a stringified JSON field in Kotlin

The posts I've seen are reversed but I need to create a JSON string from an object. I'm brand new to Kotlin but in JS I'm essentially looking for stringify.
For example:
JSON.stringify({foo: 'bar'})
That output is what I want.
I built an object like this (which, if this is wrong it doesn't need to be built this way, I'm just trying to figure this out)
val factory = JsonFactory()
val generator = factory.createGenerator(output, JsonEncoding.UTF8)
generator.writeStartObject()
generator.writeNumberField("statusCode", 200)
generator.writeObjectFieldStart("body") // The value inside specifically needs to be a JSON string!
generator.writeStringField("userId", user.id.toString())
generator.writeStringField("amount", user.amount.toString())
generator.writeStringField("percent", user.percent.toString())
generator.writeEndObject()
generator.writeEndObject()
generator.close()
This will create an object like:
{"statusCode": 200, body: {userId: ..., amount: ..., percent: ...}}
However, this won't work (working with a Lambda + API Gateway) and the body needs to be a stringified JSON blob like this:
{"statusCode": 200, body: "{\"userId\": ..., \"amount:\" ..., \"percent:\" ...}"}
I'm just not sure how to do it. Currently (so I can get something to work) I'm just concat'ing in a stringField like:
generator.writeStringField("body", "{" +
Which is very gross and hard to manage so I'd like to do it the "right" way and I have a feeling there's an easy way to do this but can't seem to find it.
kotlinx.serialization provides solution for that:
import kotlinx.serialization.*
import kotlinx.serialization.json.*
#UseExperimental(ImplicitReflectionSerializer::class)
fun main() {
val json = Json(JsonConfiguration.Default)
println(json.stringify(Response(status = 200,
body = json.stringify(Payment("abc", 100, 20)))))
}
#Serializable
data class Payment(val userId: String, val amount: Int, val percent: Int)
#Serializable
data class Response(val status: Int, val body: String)
But let's say you cannot use kotlinx.serialization, since it's experimental.
You can still use ObjectMapper from jackson library (as I see you're using their JsonFactory, which is low level):
import com.fasterxml.jackson.databind.ObjectMapper
fun main() {
val json = ObjectMapper()
println(json.writeValueAsString(Response(status = 200,
body = json.writeValueAsString(Payment("abc", 100, 20)))))
}
data class Payment(val userId: String, val amount: Int, val percent: Int)
data class Response(val status: Int, val body: String)
You can even have your own JSON.stringify:
object JSON {
fun stringify(e: Any) = ObjectMapper().writeValueAsString(e)
}
Or have it as an extension function:
fun main() {
println(Response(status = 200,
body = Payment("abc", 100, 20).asJson().toString()).asJson())
}
data class Payment(val userId: String, val amount: Int, val percent: Int)
data class Response(val status: Int, val body: String)
fun Any.asJson() = ObjectMapper().writeValueAsString(this)

How to write POJOs to get specific array inside an object from JSON using Retrofit2?

I need to do a task: paging list of news.
To do it I took a sample from googlesample/architecthurecomponents/PagingWithNetworkSample and encounter with this question. Question is about code from Google sample to parse JSON file.
JSON url: https://www.reddit.com/r/androiddev/hot.json
POJO file:
#Entity(tableName = "posts",
indices = [Index(value = ["subreddit"], unique = false)])
data class RedditPost(
#PrimaryKey
#SerializedName("name")
val name: String,
#SerializedName("title")
val title: String,
#SerializedName("score")
val score: Int,
#SerializedName("author")
val author: String,
#SerializedName("subreddit") // this seems mutable but fine for a demo
#ColumnInfo(collate = ColumnInfo.NOCASE)
val subreddit: String,
#SerializedName("num_comments")
val num_comments: Int,
#SerializedName("created_utc")
val created: Long,
val thumbnail: String?,
val url: String?) {
// to be consistent w/ changing backend order, we need to keep a data like this
var indexInResponse: Int = -1
}
and this is an API interface:
interface RedditApi {
#GET("/r/{subreddit}/hot.json")
fun getTop(
#Path("subreddit") subreddit: String,
#Query("limit") limit: Int): Call<ListingResponse>
#GET("/r/{subreddit}/hot.json")
fun getTopAfter(
#Path("subreddit") subreddit: String,
#Query("after") after: String,
#Query("limit") limit: Int): Call<ListingResponse>
#GET("/r/{subreddit}/hot.json")
fun getTopBefore(
#Path("subreddit") subreddit: String,
#Query("before") before: String,
#Query("limit") limit: Int): Call<ListingResponse>
class ListingResponse(val data: ListingData)
class ListingData(
val children: List<RedditChildrenResponse>,
val after: String?,
val before: String?
)
data class RedditChildrenResponse(val data: RedditPost)
companion object {
private const val BASE_URL = "https://www.reddit.com/"
fun create(): RedditApi = create(HttpUrl.parse(BASE_URL)!!)
fun create(httpUrl: HttpUrl): RedditApi {
val logger = HttpLoggingInterceptor(HttpLoggingInterceptor.Logger {
Log.d("API", it)
})
logger.level = HttpLoggingInterceptor.Level.BASIC
val client = OkHttpClient.Builder()
.addInterceptor(logger)
.build()
return Retrofit.Builder()
.baseUrl(httpUrl)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(RedditApi::class.java)
}
}
}
The question is: how does the API request exactly finds what we need, a children: [...], which represent a list of posts? Because a children: [...] resides inside object and in code we don't have a POJO with #Serialized("children")field. Only a pojo for items inside children: [...]. I tried to implement this approach specific to my json, but it returns a null value.
Thanks everyone for help.
You don't have to add #SerializedName annotation if the name of the field in POJO is the same as the name of the field in JSON. That's why class ListingResponse(val data: ListingData) can be mapped to
{
"kind": "Listing",
"data": ...
}

How to get a nested object in JSON in Kotlin?

I've created the classes of Eqs and Service, got the service objects but can't get the list of eqs. Can anyone help me with this?
This the Eqs class
data class Eqs(
val name: String,
val imageUrl: String,
val description: String?,
val responsible: String
)
That's the Service class which gets its values
data class Service(
val title: String,
val servings: Int,
val eqs: List<Eqs>
) {
companion object {
fun getServicesFromFile(filename: String, context: Context): ArrayList<Service> {
val serviceList = ArrayList<Service>()
try {
// Load data
val jsonString = loadJsonFromAsset("services.json", context)
val json = JSONObject(jsonString)
val services = json.getJSONArray("services")
(0 until services.length()).mapTo(serviceList) {
Service(services.getJSONObject(it).getString("title"),
services.getJSONObject(it).getInt("servings"),
}
} catch (e: JSONException) {
e.printStackTrace()
}
return serviceList
}
I can't get the List of Eqs in my getServicesFromFile function. How to parse and get it correctly?
I recommend you to use Jackson library. It's simple and saves you a lot of time. You can find it's documentation here: https://www.baeldung.com/jackson-kotlin
You also can use some websites to generate the data class needed for Jackson like https://app.quicktype.io/
Use Json to Kotlin plugin
In tool bar of android studio Code >> Generate and copy & paste you API into it and give the class name
[
{
"id": 1,
"name" : "Madoldoowa",
"description": "Madol Doova (මඩොල් දූව) is a children's novel and coming-of-age story written by Sri Lankan writer
Martin Wickramasinghe and first published in 1947",
"language" : "Sinhala",
"isbn" : "ISBN232673434",
"file_size" : 300,
"no_of_pages" : 500,
"price" : 970,
"ratings" : "5.1K",
"cover_page" : "https://upload.wikimedia.org/wikipedia/en/5/5c/MadolDoova.jpg",
"author" : {
"name" : "Martin Wickramasinghe"
}
]
data class Model(
val author: Author,
val cover_page: String,
val description: String,
val file_size: Int,
val id: Int,
val isbn: String,
val language: String,
val name: String,
val no_of_pages: Int,
val price: Int,
val ratings: String
)
data class Author(
val name: String
)

Circe Unmarshall HttpResponse

I'm trying to ask consul for healthy services.The response is:
HttpResponse(200 OK,List(X-Consul-Index: 3471242, X-Consul-Knownleader: true, X-Consul-Lastcontact: 0, Date: Fri, 02 Mar 2018 16:06:08 GMT),HttpEntity.Strict(application/json,[{"Node":{"Node":"ci-content-1","Address":"10.45.200.14","TaggedAddresses":{"wan":"10.45.200.14"},"CreateIndex":2708577,"ModifyIndex":3470978},"Service":{"ID":"de62bdcb8e37:varnish_2:6085","Service":"shop-varnish-6085","Tags":null,"Address":"10.45.200.14","Port":33889,"EnableTagOverride":false,"CreateIndex":3313055,"ModifyIndex":3313055},"Checks":[{"Node":"ci-content-1","CheckID":"serfHealth","Name":"Serf Health Status","Status":"passing","Notes":"","Output":"Agent alive and reachable","ServiceID":"","ServiceName":"","CreateIndex":2708577,"ModifyIndex":3451134}]},{"Node":{"Node":"ci-content-2","Address":"10.45.200.18","TaggedAddresses":{"wan":"10.45.200.18"},"CreateIndex":2158463,"ModifyIndex":3471241},"Service":{"ID":"f89a94600d4c:varnish_1:6085","Service":"shop-varnish-6085","Tags":null,"Address":"10.45.200.18","Port":33622,"EnableTagOverride":false,"CreateIndex":3313064,"ModifyIndex":3313064},"Checks":[{"Node":"toom-ci-content-2","CheckID":"serfHealth","Name":"Serf Health Status","Status":"passing","Notes":"","Output":"Agent alive and reachable","ServiceID":"","ServiceName":"","CreateIndex":2158464,"ModifyIndex":3297480}]}]
The class definitions are:
final case class TaggedAddresses (
wan: String)
final case class Node (
node: String,
address: String,
taggedAddresses: TaggedAddresses,
createIndex: Int,
modifyIndex: Int
)
final case class Service (
id: String,
service: String,
tags: String,
addresses: String,
port: Int,
enableTagOverride: String,
createIndex: Int,
modifyIndex: Int
)
final case class Check (
node: String,
checkId:String,
name: String,
status: String,
notes: String,
output: String,
serviceId: String,
serviceName:String,
createIndex: Int,
modifyIndex: Int
)
final case class NodeInfo(
node: Node,
service: Service,
checkList: List[Check]
)
package object VarnishInformation {}
Then I try to unmarshall:
val request = HttpRequest(method = HttpMethods.GET, uri = consulUrl)
val response = Await.result(Http().singleRequest(request), 10.seconds)
log.info("Entity: " + response.httpMessage)
val entries = Unmarshal(response).to[List[NodeInfo]]
and get the following error:
Error during processing of request: 'Attempt to decode value on failed cursor: DownField(node),DownArray'. Completing with 500 Internal Server Error response. To change default exception handling behavior, provide a custom ExceptionHandler.
Ican't see the failure, anybody else can do?
The short answer: Assuming you have all the required decoders and encoders in place, you should simply fix your case class as follows:
case class Node (
Node: String,
Address: String,
TaggedAddresses: TaggedAddresses,
CreateIndex: Int,
ModifyIndex: Int
)
I.e., you have to use the tag names exactly as they appear in your JSON.
The long answer: if I'm taking out the relevant JSON from your object as follows:
val jsonString =
"""
{
"Node":{
"Node":"ci-content-1",
"Address":"10.45.200.14",
"TaggedAddresses":{
"wan":"10.45.200.14"
},
"CreateIndex":2708577,
"ModifyIndex":3470978
},
...
"""
Then the following code will yield Right(Node(ci-content-1,10.45.200.14,TaggedAddresses(10.45.200.14),2708577,3470978)), when the above, corrected version of the case class is used:
def myParse(jsonString: String) = {
val res = parse(jsonString) match {
case Right(json) => {
val cursor = json.hcursor
cursor.get[Node]("Node")
}
case _ => Left("Wrong JSON!")
}
println(res)
}
Otherwise, I also get the same error you described.