Convert JsonFactory object into a stringified JSON field in Kotlin - json

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)

Related

Is there a way to deserialize Json into Kotlin data class and convert property types?

I have some json
{
"name": "Foo",
"age": 12,
"id": "1234567890"
}
Currently, my data class I want to deserialize into looks like this
data class Example( val name: String, val age: Int, val id: String)
Is there a way to simply have the data class as
data class Example( val name: String, val age: Int, val id: Long)
Attention to the id type Long
I suppose this can be achieved through the use of an extension function that parses the data class into a separate data classes.
But I'd like to know if this is possible in any other way. I'm using Jackson for deserialization.
You don't have to do anything. Jackson automatically converts the String into a Long if it is possible:
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
data class C(
val s: String,
val n: Long
)
fun main() {
val json = """{"s":"My string","n":"1234567890"}"""
val mapper = jacksonObjectMapper()
val c: C = mapper.readValue(json)
println(c) // prints "C(s=My string, n=1234567890)"
}
This automatic conversion is controlled by MapperFeature.ALLOW_COERCION_OF_SCALARS, which is enabled by default.

How to create a List from a HTTP response string based on certain conditions?

I am trying to parse the result of a HTTPS request in Scala.
The HTTPS response is a String as follows:
{
"rows":
[
{
"log_forwarding_ip":"",
"device_name":"AD1",
"id":"51",
"mgmt_ip_addr":"192.168.25.150",
"log_forwarding":"1",
"isActive":"0"
},
{
"log_forwarding_ip":"192.168.1.1",
"device_name":"WIN-SRV2019",
"id":"50",
"mgmt_ip_addr":"192.168.25.151",
"log_forwarding":"1",
"isActive":"1"
},
{
"log_forwarding_ip":"129.168.1.2",
"device_name":"PA",
"id":"3",
"mgmt_ip_addr":"192.168.1.161",
"log_forwarding":"1",
"isActive":"1"
}
],
"status":1
}
I have to create a List of all id's where isActive and log_forwarding are both equal to 1.
So far what I have done is:
object syncTables {
def main(args: Array[String]): Unit = {
case class deviceInfo(log_forwarding_ip: String, device_name: String, id: String,
mgmt_ip_addr: String, log_forwarding: String, isActive: String)
try {
val r = requests.get("https://192.168.1.253/api/device/deviceinfo.php", verifySslCerts = false)
if (r.statusCode == 200) {
val x = r.text
println(x)
} else {
println("Error in API call: "+r.statusCode)
}
}
}
}
Now I'm really confused what to do next to achieve my result. I'm totally new to JSON, that's why I don't know which JSON library I should use.
I tried using Play Framework but it seems all pretty complicated to me.
Does Scala offer something like Python's json module where this task can be easily done by using dictionaries and lists.
I'm using Scala 2.11.12 and com.lihaoyi.requests.Any kind of help will be highly appreciated.Thanks in advance.
Use json4s to parse the JSON string. Let's call your JSON input string json, then you can do something like this
import org.json4s._
import org.json4s.jackson.JsonMethods._
case class DeviceInfo(log_forwarding_ip: String, device_name: String, id: String,
mgmt_ip_addr: String, log_forwarding: String, isActive: String)
implicit val formats: Formats = DefaultFormats // Brings in default date formats etc.
val parsedJson = parse(json)
val deviceInfos = parsedJson match {
case JObject(head :: _) =>
head._2
.extract[List[DeviceInfo]]
.filter(_.isActive == 1 && _.log_forwarding == 1)
}
This will output
val res0: List[DeviceInfo] = List(DeviceInfo("192.168.1.1","WIN-SRV2019","50","192.168.25.151","1","1"),DeviceInfo("129.168.1.2","PA","3","192.168.1.161","1","1"))

Can't parse json in Kotlin

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
)

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
)