How to get a nested object in JSON in Kotlin? - json

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
)

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.

spray json in scala: deserializing json with unknown fields without losing them

I'm looking for a solution to this but for json spray and my searches and attempts to get this working with json spray have failed thus far.
If I have the following json:
{
"personalData": {
"person": {
"first": "first_name",
"last": "last_name"
},
"contact": {
"phone": "1111111",
"address": {
"line": "123 Main St",
"city": "New York"
}
},
"xx": "yy", // unknown in advanced
"zz": { // unknown in advanced
"aa": "aa",
"bb": "bb",
"cc": {
"dd": "dd",
"ee": "ee"
}
}
}
}
We know for sure that the json will contain person and contact but we don't know what other fields may be created upstream from us that we don't care about/use.
I want to serialize this JSON into a case class containing person and contact but on the other hand, I don't want to lose the other fields (save them in a map so the class will be deserialized to the same json as received).
This is how far I've made it:
case class Address(
line: String,
city: String,
postalCode: Option[String]
)
case class Contact(
phone: String,
address: Address
)
case class Person(
first: String,
last: String
)
case class PersonalData(
person: Person,
contact: Contact,
extra: Map[String, JsValue]
)
implicit val personFormat = jsonFormat2(Person)
implicit val addressFormat = jsonFormat3(Address)
implicit val contactFormat = jsonFormat2(Contact)
implicit val personalDataFormat = new RootJsonFormat[PersonalData] {
def write(personalData: PersonalData): JsValue = {
JsObject(
"person" -> personalData.person.toJson,
"contact" -> personalData.contact.toJson,
// NOT SURE HOW TO REPRESENT extra input
)
}
def read(value: JsValue): CAERequestBEP = ???
}
Can someone help me do this with spray.json instead of play? I've spent such a long time trying to do this and can't seem to make it work.
In order to do that, you need to write your own formatter for PersonalDataFormat:
case class Person(first: String, last: String)
case class Address(line: String, city: String)
case class Contact(phone: String, address: Address)
case class PersonalData(person: Person, contact: Contact, extra: Map[String, JsValue])
case class Entity(personalData: PersonalData)
implicit val personFormat = jsonFormat2(Person)
implicit val addressFormat = jsonFormat2(Address)
implicit val contactFormat = jsonFormat2(Contact)
implicit object PersonalDataFormat extends RootJsonFormat[PersonalData] {
override def read(json: JsValue): PersonalData = {
val fields = json.asJsObject.fields
val person = fields.get("person").map(_.convertTo[Person]).getOrElse(???) // Do error handling instead of ???
val contact = fields.get("contact").map(_.convertTo[Contact]).getOrElse(???) // Do error handling instead of ???
PersonalData(person, contact, fields - "person" - "contact")
}
override def write(personalData: PersonalData): JsValue = {
JsObject(personalData.extra ++ ("person" -> personalData.person.toJson, "contact" -> personalData.contact.toJson))
}
}
implicit val entityFormat = jsonFormat1(Entity)
val jsonResult = jsonString.parseJson.convertTo[Entity]
The result is:
Entity(PersonalData(Person(first_name,last_name),Contact(1111111,Address(123 Main St,New York)),Map(xx -> "yy", zz -> {"aa":"aa","bb":"bb","cc":{}})))
(Assuming the json is not exactly the json above, but a valid similar one)
Code run in Scastie

Kotlin: convert nested JSON object to literal string

I have a data class which has a property whose type is another data class, like this:
#Serializable
data class Vehicle (
val color: String,
val miles: Int,
val year: Int,
val garage: Garage
)
#Serializable
data class Garage (
val latitude: Float,
val longitude: Float,
val name: String
)
Upon serializing, it produces output like this:
{
"color" : "black" ,
"miles" : 35000 ,
"year" : 2017 ,
"garage" : { "latitude" : 43.478342 , "longitude" : -91.337000 , "name" : "Paul's Garage" }
}
However I would like garage to be a literal string of its JSON representation, not an actual JSON object. In other words, the desired output is:
{
"color" : "black" ,
"miles" : 35000 ,
"year" : 2017 ,
"garage" : "{ \"latitude\" : 43.478342 , \"longitude\" : -91.337000 , \"name\" : \"Paul's Garage\" }"
}
How can I accomplish this in Kotlin? Can it be done with just kotlinx.serialization or is Jackson/Gson absolutely necessary?
Note that this output is for a specific usage. I cannot overwrite the base serializer because I still need to serialize/deserialize from normal JSON (the first example). In other words, the best scenario would be to convert the first JSON sample to the second, not necessarily to have the data class produce the 2nd sample directly.
Thanks!
Create a custom SerializationStrategy for Vehicle as follows:
val vehicleStrategy = object : SerializationStrategy<Vehicle> {
override val descriptor: SerialDescriptor
get() = buildClassSerialDescriptor("Vehicle") {
element<String>("color")
element<Int>("miles")
element<Int>("year")
element<String>("garage")
}
override fun serialize(encoder: Encoder, value: Vehicle) {
encoder.encodeStructure(descriptor) {
encodeStringElement(descriptor, 0, value.color)
encodeIntElement(descriptor, 1, value.miles)
encodeIntElement(descriptor, 2, value.year)
encodeStringElement(descriptor, 3, Json.encodeToString(value.garage))
}
}
}
Then pass it to Json.encodeToString():
val string = Json.encodeToString(vehicleStrategy, vehicle)
Result:
{"color":"black","miles":35000,"year":2017,"garage":"{\"latitude\":43.47834,\"longitude\":-91.337,\"name\":\"Paul's Garage\"}"}
More info here
Here is a solution with a custom serializer for Garage and an additional class for Vehicle.
Garage to String serializer:
object GarageToStringSerializer : KSerializer<Garage> {
override val descriptor = PrimitiveSerialDescriptor("GarageToString", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: Garage) = encoder.encodeString(Json.encodeToString(value))
override fun deserialize(decoder: Decoder): Garage = Json.decodeFromString(decoder.decodeString())
}
Auxiliary class:
#Serializable
data class VehicleDto(
val color: String,
val miles: Int,
val year: Int,
#Serializable(GarageToStringSerializer::class)
val garage: Garage
) {
constructor(v: Vehicle) : this(v.color, v.miles, v.year, v.garage)
}
The demanded result can be received with:
Json.encodeToString(VehicleDto(vehicle))

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": ...
}