Populate data classes with values from linkedhashmap - kotlin

I have a linkedhashmap that has the following shape: <String, Subject>. The class Subject has the following fields:
class Subject {
var name: Boolean? = null
var lastname: Boolean? = null
var location: Boolean? = null
..
}
final_result =
"admin" -> Subject
"customer" -> Subject
etc.
I need to populate data classes that have the following format:
data class SubjectSummary(
val admin: SubjectData,
val customer: SubjectData
...
)
data class SubjectData(val details: DetailsData)
data class DetailsData(val name:String, val lastName:String ...)
Because I need to serialize the SubjectSummary class and get the following json format:
{
"admin": {
"details": {
"name": "",
"lastname": "",
...
}
}
"customer": {
"details": {
"name": "",
"lastname": "",
...
}
}
}
How do I assign the final_result map to match the SubjectSummary structure? I have done it with simple data classes, but when the fields within the data class are data classes, I'm not sure hot to populate it. Any guidance?
For simplicity I'm only showing a small example with a few fields.

If your goal with this transformation is just to be able to serialize with the given JSON format, then you don't need this SubjectSummary class. A Map<String, SubjectData> would be sufficient and probably more convenient to create when transforming from the initial map.
Also, it seems that DetailsData contains the same fields as Subject. If that's the case there is no need for an extra class either.
So in the end it seems you just need to create a Map<String, SubjectData where SubjectData could be defined as data class SubjectData(val details: Subject). You can transform your initial map pretty easily then:
val transformed = finalResult.mapValues { (_, subject) -> SubjectData(subject) }

Related

How to deserialize the string with nondeterministic keys using gson?

I have a JSON string in which the keys are not predictable.
The server returns the values with different keys with each response.
Sample JSON looks like -
{
"ids": {
"123": "08:10",
"456": "08:00"
}
}
Here, keys 123 and 345 are not fixed i.e. on the next request, my response would look as below -
{
"ids": {
"123": "08:10",
"456": "08:00"
}
}
Now, I want to parse this response into an object using GSON. So, I created the model classes as below -
data class SlotsResponse(
val ids: IDs
)
data class IDs(
val id: Map<String, String>
)
And in the code, I am trying to deserialize it as -
val response = Gson().fromJson(strResponse, SlotsResponse::class.java)
But, I am unable to get the values for IDs. They are null.
Can someone please help me to understand whatever I am trying to achieve is possible?
What you have represented with your current model contains one extra nested object. So it would represent JSONs that look like this:
{
"ids": {
"id": {
"123": "08:10",
"456": "08:00"
}
}
}
In your actual JSON, there is no field named id, so you only need the root object with the field ids, and the dynamic map:
data class SlotsResponse(
val ids: Map<String, String>
)

Generic Base Respose models in Kotlin

Everyone following is my json response:
{
"requestResponse": {
"status": 1,
"result": true,
"msg": "Success"
},
"userId": 5504
}
And following is my Base Response class:
class BaseResponses<T>{
lateinit var requestResponse: RequestResponse
}
and following are my User data class parameters.
data class User(val userId:Int)
And below as implementation:
#POST(ApiUrls.CREATE_USER)
fun createUser(#Body body: CreateUser): Single<BaseResponses<User>>
my question is that how can I access T type which is User in the Base class would highly appreciate the help.
Thanks
You don't need a genetic type - you need to inherit the properties.
data class BaseResponses { // Remove T, it's doing nothing
lateinit var requestResponse: RequestResponse
}
// Extend base class to inherit common `requestResponse` field
data class User(val userId:Int) : BaseResponses()
// User now will contain requestResponse object
#POST(ApiUrls.CREATE_USER)
fun createUser(#Body body: CreateUser): Single<User>
I might be understanding you wrong, you just want to re-use the RequestResponse class since it is generic and will be common in all your APIs. So just have it as a parameter in User data class.
So it will be like this
data class User(
val requestResponse: RequestResponse,
val userId: Int
)
Now you can simply access it directly from User object. You can even go a step further and assign it default values like this
data class User(
val requestResponse: RequestResponse = RequestResponse(),
val userId: Int = 0
)
data class RequestResponse(
val msg: String = "",
val result: Boolean = false,
val status: Int = 0
)

How to combine polymorphic and transforming serializers for nested JSON, using Kotlin serialization?

This is a simplified example of the JSON I want to work with:
{
"useless_info": "useless info",
"data": {
"useless_info2": "useless info 2",
"children": [
{
"kind": "Car",
"data": {
"id": 1,
"transmission": "manual"
}
},
{
"kind": "Boat",
"data": {
"id": 2,
"isDocked": true
}
}
]
}
}
children is an array of vehicle objects. vehicle can be boat or car.
My Problem
The information that I want is nested quite deep (the real JSON is much deeply nested). A hack solution is to model the JSON exactly by writing dozens of nested data classes that references each other. I do not want to do this.
My problem is that while I know how to use JsonTransformingSerializer to unwrap arrays of a single type, and JsonContentPolymorphicSerializer to work with objects of various types, in this situation I believe I require both, but I can't get it to work.
What I Did
Assuming a single type
I tried understanding how it would work if it was a single type.
If the objects I wanted where all of the same type, it would be trivial to implement a JsonTransformingSerializer to cut right into the data I want. In this example, I will assume I only care about the ID, so I can just create a generic Vehicle model.
#Serializable
data class VehicleResponse(
#Serializable(with = VehicleResponseSerializer::class)
#SerialName("data")
val vehicles: List<Vehicle>
)
#Serializable
data class Vehicle(val id: Int)
object VehicleResponseSerializer : JsonTransformingSerializer<List<Vehicle>>(ListSerializer(Vehicle.serializer())) {
override fun transformDeserialize(element: JsonElement): JsonElement {
val vehicles = mutableListOf<JsonElement>()
// equals: [{"kind":"Car","data":{"id":1,"transmission":"manual"}},{"kind":"Boat","data":{"id":2,"isDocked":true}}]
val vehicleArray = element.jsonObject["children"]!!.jsonArray
vehicleArray.forEach { vehicle ->
// equals: {"id":1,"transmission":"manual"}}
val vehicleData = vehicle.jsonObject["data"]!!
vehicles.add(vehicleData)
}
return JsonArray(vehicles.toList())
}
}
The code works perfectly. Calling it out from main, printing the result gives me:
VehicleResponse(vehicles=[Vehicle(id=1), Vehicle(id=2)])
But they are actually Polymorphic!
Assuming one type does not work. I need to work with Car and Boat, and call their respective functions and properties.
I tried to model the structure like this:
#Serializable
data class VehicleResponse(
#Serializable(with = VehicleResponseSerializer::class)
#SerialName("data")
val vehicles: List<Vehicle>
)
#Serializable
abstract class Vehicle {
abstract val id: Int
}
#Serializable
data class Car(
override val id: Int,
val transmission: String,
) : Vehicle()
#Serializable
data class Boat(
override val id: Int,
val isDocked: Boolean,
) : Vehicle()
What I Want
I want to receive a JSON from a server, and instantly be able to deserialize it into a list of Vehicle objects, like the one VehicleResponse has.
I want to navigate through a deeply nested JSON, and unwrap an array that contains various Vehicle objects. For this, I assume I need to use JsonTransformingSerializer.
I want to use polymorphic deserialization to convert each of Vehicle into its corresponding subtype.
The actual ACTUAL problem
The thing that is truly throwing me in a loop is that a polymorphic serializer just does not seem to fit. It's called first before I get to parse the JSON. How am I supposed to decide which serializer to use?
Here's a test implementation:
object VehiclePolymorphicSerializer: JsonContentPolymorphicSerializer<VehicleResponse>(VehicleResponse::class) {
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<out VehicleResponse> {
println("\nselectDeserializer()\n" +
"base element:\n" +
"$element\n")
// this return is a temporary hack, I just want to see the base element by printing it to the console
return VehicleResponse.serializer()
}
}
It prints:
selectDeserializer()
base element:
{"useless_info":"useless info","data":{"useless_info2":"useless info 2","children":[{"kind":"Car","data":{"id":1,"transmission":"manual"}},{"kind":"Boat","data":{"id":2,"isDocked":true}}]}}
That's the whole initial JSON! How am I supposed to decide which deserialization strategy to use, if both Car and Boat are in there? The JsonTransformingSerializer is called after the JsonContentPolymorphicSerializer.
Really not sure how am I supposed to proceed here. Would really appreciate even a slight hint.
kotlinx.serialization can handle polymorphic deserialization in this case without custom JsonContentPolymorphicSerializer. You just need to preserve class descriminator in the JSON returned from your JsonTransformingSerializer:
object VehicleResponseSerializer : JsonTransformingSerializer<List<Vehicle>>(ListSerializer(Vehicle.serializer())) {
override fun transformDeserialize(element: JsonElement): JsonElement {
// equals: [{"kind":"Car","data":{"id":1,"totalWheels":"4"}},{"kind":"Boat","data":{"id":2,"isDocked":true}}]
val vehicleArray = element.jsonObject["children"]!!.jsonArray
// equals: [{"type":"Car","id":1,"totalWheels":"4"}, {"type":"Boat","id":2,"isDocked":true}]
return JsonArray(vehicleArray.map {
val data = it.jsonObject["data"]!!.jsonObject
val type = it.jsonObject["kind"]!!
JsonObject(
data.toMutableMap().apply { this["type"] = type }
/*
//Kotlin 1.4 offers a nicer way to do this:
buildMap {
putAll(data)
put("type", type)
}
*/
)
})
}
}
If you declare Vehicle class as sealed (not just abstract), you're already good to go. If you want to keep it abstract, then you need to register all its subclasses in serializersModule
val module = SerializersModule {
polymorphic(Vehicle::class) {
subclass(Car::class)
subclass(Boat::class)
}
}
and pass it to JSON configuration
val kotlinx = Json {
ignoreUnknownKeys = true
serializersModule = module
}
UPDATE
(Alternative approach with combination of JsonTransformingSerializer & JsonContentPolymorphicSerializer)
Actually, it's possible to combine these two serializers.
For the sake of justification for this approach, let's imagine that original JSON doesn't have that nice kind field, and we have to figure out the actual subtype of Vehicle by the shape of JSON. In this case it could be the following heuristic: "if there is a isDocked field, then it's a Boat, othrewise - a Car".
Yes, we may include this logic into JsonTransformingSerializer to create class descriminator on the fly:
val type = when {
"isDocked" in data -> JsonPrimitive("Boat")
else -> JsonPrimitive("Car")
}
But it's more common (and type-safe) to use JsonContentPolymorphicSerializer for this. So we may simplify JsonTransformingSerializer:
object VehicleResponseSerializer : JsonTransformingSerializer<List<Vehicle>>(ListSerializer(Vehicle.serializer())) {
override fun transformDeserialize(element: JsonElement): JsonElement {
// equals: [{"kind":"Car","data":{"id":1,"totalWheels":"4"}},{"kind":"Boat","data":{"id":2,"isDocked":true}}]
val vehicleArray = element.jsonObject["children"]!!.jsonArray
// equals: [{"id":1,"totalWheels":"4"}, {"id":2,"isDocked":true}] // Note that class discriminator is absent here!
return JsonArray(vehicleArray.map { it.jsonObject["data"]!! })
}
}
and define JsonContentPolymorphicSerializer (for Vehicle, not for VehicleResponse!) to handle actual serializer selection:
object VehiclePolymorphicSerializer : JsonContentPolymorphicSerializer<Vehicle>(Vehicle::class) {
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<out Vehicle> = when {
"isDocked" in element.jsonObject -> Boat.serializer()
else -> Car.serializer()
}
}
Since JsonTransformingSerializer (aka VehicleResponseSerializer) is registered for vehicles field serialization it's called before JsonContentPolymorphicSerializer (aka VehiclePolymorphicSerializer). Actually, the latter one is not yet called at all. We need to explicitly register it in serializersModule and pass it to JSON configuration (regardless of whether Vehicle class is declared as abstract or sealed):
val module = SerializersModule {
polymorphicDefault(Vehicle::class) { VehiclePolymorphicSerializer }
}
val kotlinx = Json {
ignoreUnknownKeys = true
serializersModule = module
}

Spring JPA Projection not working with UUID types?

I have a project with Kotlin and Springboot.
In this project an entity have this following fields: id, name and parent.
What i want is to only get the id and the name. So i made a projection interface view for that entity.
This is my entity:
#Entity
data class Keyword(
#Id
#GeneratedValue(generator = "uuid2")
#GenericGenerator(name = "uuid2", strategy = "uuid2")
#Column(columnDefinition = "BINARY(16)")
var id: UUID? = null,
#Column(columnDefinition = "BINARY(16)")
var parent: UUID? = null,
#NotBlank
var name: String? = null
)
Repository:
#Repository
interface KeywordRepository: JpaRepository<Keyword, UUID> {
#Query(value = "SELECT keyword.id, keyword.parent, keyword.name FROM keyword LEFT JOIN project_keyword ON project_keyword.keyword_id = keyword.id WHERE project_keyword.project_id LIKE :id", nativeQuery = true)
fun findAllKeywordsByProjectId(id: UUID): MutableList<KeyWordView>
}
Service:
#Service
class KeywordService (
private val projectService: ProjectService,
private val keywordRepository: KeywordRepository
) {
fun getKeywordsByProjectId(id: UUID): MutableList<KeyWordView> {
projectService.checkIfProjectExistsById(id)
return keywordRepository.findAllKeywordsByProjectId(id).toMutableList()
}
}
My projection interface class:
interface KeyWordView {
val id: UUID
val name: String?
}
When i call this endpoint via controller class. I get this output:
"list": [
{
"name": "MARINE MICROBIOTA",
"id": "0,31,-36,77,29,123,66,-25,-127,-43,-31,83,104,-90,47,10"
}
]
But if i change the val id: String to val id: UUID in my KeywordView interface, i get this following error:
{
"code": 500,
"data": null,
"message": "Could not write JSON: Projection type must be an interface!; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Projection type must be an interface! (through reference chain: no.inmeta.ris.util.response.ResponseDto[\"data\"]->no.inmeta.ris.util.pagination.PageDto[\"list\"]->java.util.ArrayList[0]->com.sun.proxy.$Proxy199[\"id\"])",
"status": "FAIL"
}
Anyone know how to solve this problem? I want to receive the UUID as UUID not with the strange format.
Thank you!
I guess the problem is the UUID Class. As your entity states you defined dir hibernate that the column in db is a binary(16). And i suppose using this UUID Type in the Interface projection does not Work since there is no valid mapping information (how to transform the data coming as binary with a length of 16 bytes to the UUID Class). So I assume you have to change the Type of your column or you have to use string and write a function to Transform that String.
Another possibility is to create a second entity for that table with Just the two cols you want. And Just use that second dao as your are using yiur projection.

GSON throwing Expected BEGIN_OBJECT but was BEGIN_ARRAY

Facing this problem past few days.Does any guys check this issue? Any help would be greatly appreciated.How can I solved this problem?
GSON throwing Expected BEGIN_OBJECT but was BEGIN_ARRAY
Problem coming from
override fun onSuccess(str_SUCCESS: String)
{
System.out.println("JSON_IS"+str_SUCCESS)
val paymentScheduleModel = Gson().fromJson<PaymentScheduleModel>(str_SUCCESS, PaymentScheduleModel::class.java) // Problem here
}
Json Response is
{
"status": {
"statusCode": 10016,
"isSuccess": true,
"message": "Success"
},
"data": {
"payback_schedule": [
{
"id": 2,
"paid_amount": "INR NaN",
"paidStatus": "Upcoming Payback",
"paid_status": "P",
"s_date": "05/01/2018 12:31:10",
"e_date": "11/01/2018 12:31:10",
"current_balance": "INR 399",
"payanytime_button_status": "active",
"btnColor": "red",
"btnHexColor": "#D2322D"
},
{
"id": 3,
"paid_amount": "INR NaN",
"paidStatus": "Upcoming Payback",
"paid_status": "P",
"s_date": "12/01/2018 12:31:10",
"e_date": "18/01/2018 12:31:10",
"current_balance": "INR 399",
"payanytime_button_status": "active",
"btnColor": "red",
"btnHexColor": "#D2322D"
}
]
}
}
PaymentScheduleModel
data class PaymentScheduleModel(#SerializedName("payback_schedule") val payback_schedule: PaymentSchedule)
data class PaymentSchedule
(#SerializedName("id") val id: Int,
#SerializedName("paid_amount") val paid_amount:String,
#SerializedName("paidStatus") val paidStatus:String,
#SerializedName("paid_status") val paid_status:String,
#SerializedName("s_date") val s_date:String,
#SerializedName("e_date") val e_date:String,
#SerializedName("current_balance") val current_balance:String,
#SerializedName("payanytime_button_status") val payanytime_button_status:String,
#SerializedName("btnColor") val btnColor:String,
#SerializedName("btnHexColor") val btnHexColor:String)
Your model object does not match your Json.
You are trying to parse a JsonObject PaymentScheduleModel which has sub object "payback_schedule" of type PaymentSchedule but you have a JsonObject which has a sub object "data" which is what has the sub object "payback_schedule". So really, you want to parse the "data" block.
You have two options:
1: Create another model that wraps the data block and parse that:
data class PaymentScheduleData(#SerializedName("data") val payback_schedule_model: PaymentScheduleModel)
override fun onSuccess(str_SUCCESS: String) {
val paymentScheduleData = Gson().fromJson<PaymentScheduleData>(str_SUCCESS, PaymentScheduleData::class.java)
// Now use paymentScheduleData.payback_schedule_model
}
2: Pull out the data portion first, then parse:
override fun onSuccess(str_SUCCESS: String) {
// Get the root JsonObject
val jsonObject = Gson().fromJson<JsonObject>(str_SUCCESS, JsonObject::class.java)
// Get the "data" block that matches the model and parse that
val paymentScheduleModel = Gson().fromJson<PaymentScheduleModel>(jsonObject.getAsJsonObject("data"), PaymentScheduleModel::class.java)
}
Hope that helps!
The error is telling you that payback_schedule is holding an array instead of object. So, payback_schedule should be Array<PaymentSchedule> instead of PaymentSchedule.
data class PaymentScheduleModel(#SerializedName("payback_schedule") val payback_schedule: Array<PaymentSchedule>)
PS. You are suggested to implement your own equals() and hashCode() function if your data class contains Array because the default implementation of Array's equals() function compares the referential equality. Suggested reading: Equals method for data class in kotlin