Kotlin object deserialization with GSON - gson

I have a class
class ThreadComment(
banned: Int,
closed: Int,
comment: String?,
date: String?,
email: String?,
files: ArrayList<File>?,
lasthit: Int,
name: String?,
num: String?,
op: Int,
parent: String?,
postsCount: Int,
sticky: Int,
subject: String?,
tags: String?,
timestamp: Int,
trip: String?) : Comment(banned, closed, comment, date, email, files, lasthit, name, num, op, parent, postsCount, sticky, subject, tags, timestamp, trip) {
val answers: ArrayList<String> = ArrayList()}
Parent class looks like
open class Comment(
#com.google.gson.annotations.SerializedName("banned")
val banned: Int = 0,
#com.google.gson.annotations.SerializedName("closed")
val closed: Int = 0,
#com.google.gson.annotations.SerializedName("comment")
val comment: String? = null,
#com.google.gson.annotations.SerializedName("date")
val date: String? = null,
#com.google.gson.annotations.SerializedName("email")
val email: String? = null,
#com.google.gson.annotations.SerializedName("files")
val files: ArrayList<File>? = null,
#com.google.gson.annotations.SerializedName("lasthit")
val lasthit: Int = 0,
#com.google.gson.annotations.SerializedName("name")
val name: String? = null,
#com.google.gson.annotations.SerializedName("num")
val num: String? = null,
#com.google.gson.annotations.SerializedName("op")
val op: Int = 0,
#com.google.gson.annotations.SerializedName("parent")
val parent: String? = null,
#com.google.gson.annotations.SerializedName("posts_count")
val postsCount: Int = 0,
#com.google.gson.annotations.SerializedName("sticky")
val sticky: Int = 0,
#com.google.gson.annotations.SerializedName("subject")
val subject: String? = null,
#com.google.gson.annotations.SerializedName("tags")
val tags: String? = null,
#com.google.gson.annotations.SerializedName("timestamp")
val timestamp: Int = 0,
#com.google.gson.annotations.SerializedName("trip")
val trip: String? = null) : Serializable
But after deserialization with GSON answers field is null.
I also tried to put initiation into init{}, but it still null, by lazy throws an exception about calling lazy.getValue on a null object reference

That´s weird, I just used #SerializedName normally as in Java with a data class and it worked:
data class(#SerializedName("my_string_value") val myStringValue: String, #SerializedName("my_int_value") val myIntValue: Int)

As described in this answer Gson can unsafely handle no-args constructor scenarios. It does so by leveraging UnsafeAllocator that in turn uses allocateInstanсe(Class p). This means that when there is no no-args constructor and there is no custom instance creator registered the object created by Gson will have fields initialized with their declared types default values.
To resolve that either add a no-arg constructor i.e
private constructor() : this(-1, -1, "", "", "", null, -1, "", "", -1, null, -1, -1, null, null, -1, null)
Or add default parameter values to your primary constructor like so:
class ThreadComment(
banned: Int = -1,
closed: Int = -1,
comment: String? = null,
date: String? = null,
email: String? = null,
files: ArrayList<File>? = null,
lasthit: Int = -1,
name: String? = null,
num: String? = null,
op: Int = -1,
parent: String? = null,
postsCount: Int = -1,
sticky: Int = -1,
subject: String? = null,
tags: String? = null,
timestamp: Int = -1,
trip: String? = null
) : Comment(banned, closed, comment, date, email, files, lasthit, name, num, op, parent, postsCount, sticky, subject, tags, timestamp, trip) {
val answers: ArrayList<String> = ArrayList()
}

Related

Kotlin MVVM, How to get the latest value from Entity in ViewModel?

I have created an app where I try to insert a record with the latest order number increased by one.
The main function is triggered from Activity, however, the whole process is in my ViewModel.
Issue no 1, After I insert a new record the order by number is not updated.
Issue no 2, When I insert first record the order by number is null, for that reason I am checking for null and setting the value to 0.
My goal here is to get the latest order_by number from Entity in my ViewModel, increased by 1 and add that new number to my new record using fun addTestData(..).
Entity:
#Entity(tableName = "word_table")
data class Word(
#ColumnInfo(name = "id") val id: Int,
#ColumnInfo(name = "word") val word: String,
#ColumnInfo(name = "order_by") val orderBy: Int
Dao:
#Query("SELECT order_by FROM word_table ORDER BY order_by DESC LIMIT 1")
suspend fun getHighestOrderId(): Int
Repository:
#Suppress("RedundantSuspendModifier")
#WorkerThread
suspend fun getHighestOrderId(): Int {
return wordDao.getHighestOrderId()
}
ViewModel:
private var _highestOrderId = MutableLiveData<Int>()
val highestOrderId: LiveData<Int> = _highestOrderId
fun getHighestOrderId() = viewModelScope.launch {
val highestOrderId = repository.getHighestOrderId()
_highestOrderId.postValue(highestOrderId)
}
fun addTestData(text: String) {
for (i in 0..1500) {
getHighestOrderId()
var highestNo = 0
val highestOrderId = highestOrderId.value
if (highestOrderId == null) {
highestNo = 0
} else {
highestNo = highestOrderId
}
val addNumber = highestNo + 1
val word2 = Word(0, text + "_" + addNumber,addNumber)
insertWord(word2)
}
}
Activity:
wordViewModel.addTestData(text)

How to improve my Room database architecture?

I am new to databases. I am not a professional developer. I would like your advice. I want to create a database that manages the students in a class. Students belong to only one class. I present to you my model. Can you let me know if this is correct please?
My data class: Gru
#Entity(tableName = "groupe_table")
data class Gru (
#PrimaryKey(autoGenerate = true) #ColumnInfo(name = "idGroup") var idGroup: Int=0,
#ColumnInfo(name = "nameGroupG") var nameGroupG : String
):Parcelable
#Entity(tableName = "user_table", foreignKeys = arrayOf(
ForeignKey(entity = Gru::class,
parentColumns = arrayOf("idGroup"),
childColumns = arrayOf("id"),
onDelete = ForeignKey.CASCADE)
))
#Parcelize
data class User(#PrimaryKey(autoGenerate = true) #ColumnInfo(name = "id") var id: Int=0,
#ColumnInfo(name = "nameGroup") var nameGroup: String,
#ColumnInfo(name = "firstName") var firstName: String,
#ColumnInfo(name = "lastName") var lastName: String,
#ColumnInfo(name = "nbTeam") var nbTeam: String
):Parcelable
#Entity(tableName = "eval_table", foreignKeys = arrayOf(
ForeignKey(entity = User::class,
parentColumns = arrayOf("id"),
childColumns = arrayOf("idEval"),
onDelete = ForeignKey.CASCADE)
))
data class Eval(#PrimaryKey(autoGenerate = true) #ColumnInfo(name = "idEval") var idEval: Int=0,
#ColumnInfo(name = "note_classement") var note_classement: String,
#ColumnInfo(name = "note_attaque") var note_attaque: String,
#ColumnInfo(name = "note_passe") var note_passe: String,
#ColumnInfo(name = "note_afl2") var note_afl2: String,
#ColumnInfo(name = "note_afl3") var note_afl3: String,
#ColumnInfo(name = "note_sur_vingt") var note_sur_vingt: String)
Here my dataclass to create relations
data class GruWithUser(
var idGroup: Int,
var nameGroupG: String,
var id: Int,
var nameGroup: String,
var firstName: String,
var lastName: String,
var nbTeam: String
):Parcelable
and the last dataclass relation: User With Eval
#Parcelize
data class UserWithEval(
var id: Int,
var nameGroup: String,
var firstName: String,
var lastName: String,
var nbTeam: String,
var note_attaque: String,
var note_passe: String,
var note_classement: String,
var note_afl2: String,
var note_afl3: String,
var note_sur_vingt: String
): Parcelable
Thanks you so much for your help
Issue 1
You appear to have an issue that will likely cause some frustration if not addressed.
That is a User, has it's primary key as the reference to the parent group (Gru). As such a Group could only have a single User (Student) as the primary key, for the User must be unique.
Likewise for Eval's.
So you could consider the following:-
#Entity(tableName = "groupe_table")
data class Gru (
#PrimaryKey(autoGenerate = true) #ColumnInfo(name = "idGroup") var idGroup: Int=0,
#ColumnInfo(name = "nameGroupG") var nameGroupG : String
): Parcelable
#Entity(tableName = "user_table", foreignKeys = arrayOf(
ForeignKey(entity = Gru::class,
parentColumns = arrayOf("idGroup"),
//childColumns = arrayOf("id"), //<<<<< REMOVED
childColumns = ["gru_id_reference"], //<<<<< REPLACED WITH
onDelete = ForeignKey.CASCADE)
))
#Parcelize
data class User(#PrimaryKey(autoGenerate = true) #ColumnInfo(name = "id") var id: Int=0,
#ColumnInfo(name = "nameGroup") var nameGroup: String,
#ColumnInfo(name = "firstName") var firstName: String,
#ColumnInfo(name = "lastName") var lastName: String,
#ColumnInfo(name = "nbTeam") var nbTeam: String,
#ColumnInfo(index = true) //<<<<< ADDED (may be more efficient)
var gru_id_reference: Int //<<<<< ADDED
):Parcelable
#Entity(tableName = "eval_table", foreignKeys = arrayOf(
ForeignKey(entity = User::class,
parentColumns = arrayOf("id"),
childColumns = arrayOf("user_id_reference"), //<<<<< CHANGED
onDelete = ForeignKey.CASCADE)
))
data class Eval(#PrimaryKey(autoGenerate = true) #ColumnInfo(name = "idEval") var idEval: Int=0,
#ColumnInfo(name = "note_classement") var note_classement: String,
#ColumnInfo(name = "note_attaque") var note_attaque: String,
#ColumnInfo(name = "note_passe") var note_passe: String,
#ColumnInfo(name = "note_afl2") var note_afl2: String,
#ColumnInfo(name = "note_afl3") var note_afl3: String,
#ColumnInfo(name = "note_sur_vingt") var note_sur_vingt: String,
#ColumnInfo(index = true) var user_id_reference: Int //<<<<< ADDED
)
See the comments
note that there is no need to use #ColumnInfo to name a column the same name as the field/member (hence the added code doesn't, but instead uses the annotation to introduce an index on the additional column use to reference the parent).
Without going into all the other code, the following code:-
db = TheDatabase.getInstance(this)
dao = db.getAllDao()
val g1id = dao.insert(Gru(nameGroupG = "Group001"))
val g2id = dao.insert(Gru(nameGroupG = "Group002"))
val g3id = dao.insert(Gru(nameGroupG = "group003"))
val u1id = dao.insert(User(nameGroup = "Why have this here?", firstName = "Fred", lastName = "Bloggs", nbTeam = "TeamA", gru_id_reference = g1id.toInt()))
val u2id = dao.insert(User(nameGroup = "?????", firstName = "Jane", lastName = "Doe", nbTeam = "TeamX", gru_id_reference = g1id.toInt()))
val u3id = dao.insert(User(nameGroup = "?????", firstName = "Mary", lastName = "Smith", nbTeam = "TeamB", gru_id_reference = g2id.toInt()))
val u4id = dao.insert(User(nameGroup = "?????", firstName = "Tom", lastName = "Cobbely", nbTeam = "TeamC", gru_id_reference = g3id.toInt()))
var baseEval = Eval(note_classement = "CMENT_", note_attaque = "ATTQ_", note_afl2 = "AFL2_", note_afl3 = "AFL3_", note_passe = "PASSE_", note_sur_vingt = "SV_",user_id_reference = -99)
for (i in 1..10) {
dao.insert(
Eval(
note_classement = baseEval.note_classement + i,
note_attaque = baseEval.note_classement + i,
note_afl2 = baseEval.note_afl2 + i,
note_afl3 = baseEval.note_afl3 + i,
note_passe = baseEval.note_passe + i,
note_sur_vingt = baseEval.note_sur_vingt + i,
user_id_reference = Random.nextInt(4) + 1
)
)
}
results in a database (i.e. tests the changed code) as per :-
The 3 Groups (Gru's/Classes) :-
The 4 users :-
Note how Fred and Jane are both in Group001
And the 10 Eval's spread across the 4 Users
A query that joins the data according to the relationships looks like:-
- here you can see that there are 3 evaluations for Group001, 2 of them for Fred and 1 for Jane etc
- (note Eval's reference a random User)
The above data was obtained by running the code above and then using App Inspection (available in Android Studio).
Issue 2
You may well encounter subsequent issues due to both the user_table and the eval_table having a column named id. From an SQL point of view this can be overcome by qualifying the column with it's table name (see SQL used above where the tablename . column is used to disambiguate the ambiquity). However, as far as the resultant output there would still be 2 id columns. This ambiguity can be overcome using AS to rename the output column but you may then encounter issues. I would suggest ensuring that all column names are unique (so perhaps have column names userId and evalId instead of just id).

Exceptions not captured in opencsv parsing

I'm trying to parse a csv file and map it onto a data class. I've setup some validations for the columns and I'm testing it by sending incorrect values for those columns. opencsv throws a generic exception
Basic instantiation of the given bean type (and subordinate beans created through recursion, if applicable) was determined to be impossible.
Code details
Data class:
data class UserInfo(
#CsvBindByName(column = "Id", required = true) val id: Long,
#CsvBindByName(column = "FirstName", required = true) val firstName: String,
#CsvBindByName(column = "LastName", required = true) val lastName: String,
#CsvBindByName(column = "Email", required = true) val email: String,
#CsvBindByName(column = "PhoneNumber", required = true) val phoneNumber: String,
#PreAssignmentValidator(
validator = MustMatchRegexExpression::class, paramString = "^[0-9]{10}$")
#CsvBindByName(column = "Age", required = true)
val age: Int
)
csv parsing logic
fun uploadCsvFile(file: MultipartFile): List<UserInfo> {
throwIfFileEmpty(file)
var fileReader: BufferedReader? = null
try {
fileReader = BufferedReader(InputStreamReader(file.inputStream))
val csvToBean = createCSVToBean(fileReader)
val mappingStrategy: HeaderColumnNameMappingStrategy<Any> =
HeaderColumnNameMappingStrategy<Any>()
mappingStrategy.type = UserInfo::class.java
val userInfos = csvToBean.parse()
userInfos.stream().forEach { user -> println("Parsed data:$user") }
csvToBean.capturedExceptions.stream().forEach { ex -> println(ex.message) }
return userInfos
} catch (ex: Exception) {
throw CsvImportException("Error during csv import")
} finally {
closeFileReader(fileReader)
}
}
private fun createCSVToBean(fileReader: BufferedReader?): CsvToBean<UserInfo> =
CsvToBeanBuilder<UserInfo>(fileReader)
.withType(UserInfo::class.java)
.withThrowExceptions(false)
.withIgnoreLeadingWhiteSpace(true)
.build()
I'm looking for the proper error message for the validation / missing field so that I can communicate it to the error response.

Kotlin iterator to check if payload list have an id/projectId or not, returning false when there is no attributes?

I have an issue where i have a method where i am checking the payload has the attributes or not. When i am sending my payload i want to check that the user dont have inserted attributes which not allowed in the payload.
My entity class:
#Entity
data class ProjectAssociated(
#Id
#GeneratedValue(generator = "uuid2")
#GenericGenerator(name = "uuid2", strategy = "uuid2")
#Column(columnDefinition = "BINARY(16)")
var id: UUID? = null,
#Column(columnDefinition = "BINARY(16)")
var projectId: UUID? = null,
#Column(columnDefinition = "BINARY(16)")
var associatedProjectId: UUID? = null
)
My Service class:
fun addAssociatedProjectByProjectId(
projectId: UUID,
projectAssociatedList: MutableList<ProjectAssociated>
): MutableList<ProjectAssociated> {
if (projectAssociatedList.isNotEmpty()) {
println(projectAssociatedList)
if (!projectAssociatedList.map { it.id }.isNullOrEmpty()) {
val errorMessage = "Not allowed to provide parameter 'id' in this request"
throw UserInputValidationException(errorMessage)
}
if (!projectAssociatedList.map { it.projectId }.isNullOrEmpty()) {
val errorMessage = "Not allowed to provide parameter 'projectId' in this request"
throw UserInputValidationException(errorMessage)
}
val checkIds = projectAssociatedList.map {
projectRepository.existsById(it.associatedProjectId)
}
if (checkIds.contains(false)) {
val errorMessage = "One or more ID 'associatedProjectId' not exists"
throw UserInputValidationException(errorMessage)
}
}
return projectAssociatedList.map {
projectAssociatedRepository.save(
ProjectAssociated(
null,
projectId,
it.associatedProjectId
)
)
}.toMutableList()
}
My Controller class:
#ApiOperation("Add associated Projects to a specific Project")
#PostMapping(path = ["/project-associated"], consumes = [MediaType.APPLICATION_JSON_VALUE])
fun createAssociatedProjectList(
#ApiParam("The id of the Project", required = true)
#RequestParam("id")
id: UUID,
#ApiParam("JSON object representing the ProjectAssociated")
#RequestBody projectAssociated: MutableList<ProjectAssociated>
): ResponseEntity<WrappedResponse<MutableList<ProjectAssociated>>> {
val createdProjectAssociatedList = projectService.addAssociatedProjectByProjectId(id, projectAssociated)
return ResponseEntity
.status(201)
.location(URI.create("$id/project-associated"))
.body(
ResponseDto(
code = 201,
data = PageDto(list = mutableListOf(createdProjectAssociatedList))
).validated()
)
}
But when i try to send this payload with the project id in #RequestParam:
[
{
"associatedProjectId": "7fe40f90-5178-11ea-9136-1b65a920a5d9"
},
{
"associatedProjectId": "7fe8aaaa-5178-11ea-9136-1b65a920a5d9"
}
]
I have a custom exception where i tell the user if projectId or the id is in the payload that is now allowed to have it in the payload. When i try to POST the payload example above it tells me that projectId or id is in the request? How can that be?
I also printed out the list before if checks:
[ProjectAssociated(id=null, projectId=null, associatedProjectId=7fe40f90-5178-11ea-9136-1b65a920a5d9), ProjectAssociated(id=null, projectId=null, associatedProjectId=7fe8aaaa-5178-11ea-9136-1b65a920a5d9)]
What am I doing wrong?
Thanks for the help!
In the block projectAssociatedList.map { it.id } you are mapping your list to something like [null, null] and it is not null or empty.
So, the complete condition !projectAssociatedList.map { it.id }.isNullOrEmpty() returns true.
If you want to continue using the same logic, you should use !projectAssociatedList.mapNotNull { it.id }.isNullOrEmpty() instead.
The mapNotNull function will filter the null values and output a list just with the not null values. If there is only null values, the list will be empty.
But, a simpler and expressive way to check if there is any not null attribute in a list of objects could be projectAssociatedList.any { it.id != null }

Can #SqlResultSetMapping be used to map a complex Dto object

I currently have a named native query set up in CrudRepository where I'm joinnig few tables and I need to map that query result into a Dto.
select
event_id, replaced_by_match_id, scheduled, start_time_tbd, status, away_team_competitor_id, home_team_competitor_id, round_round_id, season_season_id, tournament_tournament_id, venue_venue_id,
competitorHome.competitor_id as home_competitor_competitor_id, competitorHome.abbreviation as home_competitor_competitor_abbreviation, competitorHome.country_code as home_competitor_ccountry_code, competitorHome.ioc_code as home_competitor_ioc_code, competitorHome.rotation_number as home_competitor_rotation_number, competitorHome.virtual as home_competitor_virtual,
competitorAway.competitor_id as away_competitor_competitor_id, competitorAway.abbreviation as away_competitor_competitor_abbreviation, competitorAway.country_code as away_competitor_ccountry_code, competitorAway.ioc_code as away_competitor_ioc_code, competitorAway.rotation_number as away_competitor_rotation_number, competitorAway.virtual as away_competitor_virtual,
homeTeamTranslation.competitor_competitor_id as home_team_translation_competitor_competitor_id, homeTeamTranslation.language_language_id as home_team_translation_language_language_id, homeTeamTranslation.competitor_name as home_team_translation_competitor_name, homeTeamTranslation.competitor_country as home_team_competitor_country,
awayTeamTranslation.competitor_competitor_id as away_team_translation_competitor_competitor_id, awayTeamTranslation.language_language_id as away_team_translation_language_language_id, awayTeamTranslation.competitor_name as away_team_translation_competitor_name, awayTeamTranslation.competitor_country as away_team_competitor_country
from "event" as e
left join competitor as competitorAway on competitorAway.competitor_id = e.away_team_competitor_id
left join competitor as competitorHome on competitorHome.competitor_id = e.home_team_competitor_id
left join competitor_translation as homeTeamTranslation on competitorHome.competitor_id = homeTeamTranslation.competitor_competitor_id
left join competitor_translation as awayTeamTranslation on competitorAway.competitor_id = awayTeamTranslation.competitor_competitor_id
where awayTeamTranslation.language_language_id = 'en' and homeTeamTranslation.language_language_id = 'en'
I'm trying to use #SqlResultSetMapping annotation to map result into Dto classes but unsuccessfully.
I've set up mapping this way
#SqlResultSetMapping(
name = "mapLocalizedEvent",
classes = [ConstructorResult(
targetClass = TranslatedLocalEvent::class,
columns = arrayOf(
ColumnResult(name = "event_id"),
ColumnResult(name = "scheduled"),
ColumnResult(name = "start_time_tbd"),
ColumnResult(name = "status"),
ColumnResult(name = "replaced_by_match_id")
)
)]
)
and it is working fine where all of the ColumnResult used are simple types String or Boolean. It maps to object TranslatedLocalEvent looking like this
class TranslatedLocalEvent(
val eventId: String? = null,
val scheduled: String? = null,
val startTimeTbd: Boolean? = null,
val status: String? = null,
val replacedByMatchId: String? = null
)
Is there a way I can use this approach to map a complex object? TranslatedLocalEvent object needs to contain TranslatedLocalCompetitor object built from parts of columns query returnes
class TranslatedLocalEvent(
val eventId: String? = null,
val scheduled: String? = null,
val startTimeTbd: Boolean? = null,
val status: String? = null,
val replacedByMatchId: String? = null,
val homeTeam: TranslatedLocalCompetitor? = null
)
public class TranslatedLocalCompetitor(
val competitorId: String? = null
val competitorName: String? = null
val competitorCountry: String? = null
)
The easiest way i see is in your TranslatedLocalEvent constructor accept all columns and in the contstructor create and assign the TranslatedLocalCompetitor object.

Resources